jQuery中extend方法原理浅析

JS的世界里,对象就是JS的基石,如果想要在前端的JS方向有更好的发展,那么更多的了解对象时必不可少的,这里就在jQueryextend的基础上,看看对象扩展的实现方法。

概况

给对象添加新的方法或者属性,最根本的方法就是在对象上直接添加即可。其实这样添加的话,会显得有些乱,因为你必须一个一个的添加。


var obj = {
    name:"zhang",
    sayName:function(){
	return this.name;
    }
}


想要添加两个新的属性和方法sex,和saySex,那么只能一个一个的添加


obj.sex = "male";
obj.saySex = function(){
    return this.sex;
}


不能统一的把N多个方法,直接添加进去,至于为什么会有这样,请参考:原型链断链的原因,虽然名字看起来并没有什么联系,但是其根本原因却是一个相同的原因。

并且,有时候,多个对象,要同时扩展一些相同的而方法,那么就要对每个对象进行一遍上面的写法,代码的复用性也太差了,当然,也可以使用下面的方法统一进行赋值


obj1.sex = obj.sex = "male";


只是,这样的写法,对于obj1obj的耦合性就太强了,上面这只是继承一个方法和属性,看起来还算是简单,如果一个对象要扩展很多个呢,所以,为了能更好的掌控扩展,使得扩展变得更简单明了,就专门出现了extend方法,来实现对象的扩展。

最简单的实现

最简单的实现方法,就是什么都不需要考虑直接把待扩展的方法和属性,直接copy一份到目标对象,所以实现方法如下:


function extend(obj,prev){
    var i,v;

    for(i in prev){
	obj[i] = prev[i];
    }
}

//这里即没有考虑,prev中的属性和方法,是否有意义,
//也没有考虑,prev[i]是什么类型的值,
//更没有考虑,copy这个属性,是不是在原obj对象中,
//是已经存在的属性,直接就是一股脑的把所有能遍历的属性,
//都覆盖性的添加到目标对象obj中去了。

//使用方法呢,也就是很简单的那种:

var a = {
    name:"zhang",
    sayName:function(){
	return this.name;
    }
},
b = {
    sex:"male",
    saySex:function(){
 	return this.sex;
    }
}

extend(a,b);
//把b对象中的所有方法,添加到a对象中
console.log(a);
console.log(b);
//可以查看,a对象和b对象中,包含的属性和方法
//但是有一点事要注意的,就是
//扩展来的方法,还是和原方法共用的,就像是原型链中的方法,对于每个实例,都是共享的。

console.log(a.saySex === b.saySex);  //true

//当然,这里如果重写b.saySex方法,不会对a.saySex方法有影响,
//这是因为,在重写b.saySex方法时,也是一个重新赋值的过程,
//关于赋值在JS中的影响,请参考:浅析赋值表达式–JS基础核心之一(http://www.zhangyunling.com/?p=140)

//但是,如果我假设b对象的样子是下面的样子呢
b = {
    sex:"male",
    method:{
	sex:"female",
	get:function(){
	    return b.sex;
	},
	set:function(sex){
	    b.sex = sex;
	}
    }
}

extend(a,b);
console.log(a.method.get());//male

b.method.get = function(){
    return this.sex;
}

console.log(a.method.get());//female

//为什么呢,因为method没有变啊~~~所以,
//新的方法,依然会影响到a对象被扩展过来的对应的方法。

//这也就是深copy和浅copy的根本区别所在了。


jQuery中的实现方法

没有什么需要多说的,还是直接上代码吧:


jQuery.extend = jQuery.fn.extend = function() {
    var src, copyIsArray, copy, name, options, clone,
	target = arguments[0] || {},
	//target为目标元素,所以取值为第一个传入的参数值
	//但是有个问题,就是传入的第一个参数是false时,
	//这里就会把target变成一个空对象
	//个人感觉,这里这样的处理,并不是最好的。
	i = 1,
	//跳过目标元素,把后面的元素扩展到target元素上
	length = arguments.length,
	//传入的参数的个数,用于循环把所有的元素扩展到target对象上
	deep = false;
	//deep,默认执行浅copy

    // Handle a deep copy situation
    if ( typeof target === "boolean" ) {
	//因为上面定义target时的处理,这里只会对true时,才能执行
	deep = target;
	target = arguments[1] || {};
	// skip the boolean and the target
	i = 2;
    }

    // Handle case when target is a string or something (possible in deep copy)
    if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
	//防止传入的第一个参数是undefined,null,string等
	//非boolean和object类型的数据时的处理。
	target = {};
    }

    // extend jQuery itself if only one argument is passed
    if ( length === i ) {
	//这里来判断,target是添加到jQuery对象之上
	//还是添加到一个新的对象上面
	//其实质也就是,如果传入的参数中,最多只有一个是对象
	//也就是说,只有下面的两种情况下,才会把新的方法
	//扩展到jQuery对象上
	//1:extend({});  只传入一个对象,则浅copy到jQuery对象上
	//2:extend(true,{});第一个传入true,第二个传入对象,深copy
	target = this;
	--i;
    }

    for ( ; i < length; i++ ) {
	//循环遍历待添加的对象
	// Only deal with non-null/undefined values
	if ( (options = arguments[ i ]) != null ) {
	    //只有当值有效时,才执行操作
	    // Extend the base object
	    for ( name in options ) {
		src = target[ name ];
		copy = options[ name ];

		// Prevent never-ending loop
		if ( target === copy ) {
		    //如果是属性值,那么相等就是相等
		    //如果是引用类型的值,那么只有两个指向了同一个引用类型,
		    //才会是相等,所以,如果相等,则可以直接跳过
					
		    //这里是为了防止,对象时一个循环的对象
		    //比如:a = {init:function(){},pars:"123"};
		    //b = {init:a.init,pad:"ad"}
		    //如果要把b扩展到a对象上,缺失了该部分的判断,
		    //将会导致这个扩展成为一个死循环
		    continue;
		}

		// Recurse if we're merging plain objects or arrays
		if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
		    //深复制,只有在deep=true,copy存在,
		    //copy是一个单纯对象,
				
		    if ( copyIsArray ) {
			//如果该次的copy是一个数组
			copyIsArray = false;
			//copyIsArray初始化,以便于下次循环调用
						
			//clone定义为下次extend中的目标对象
			clone = src && jQuery.isArray(src) ? src : [];

		    } else {
			//clone是根据src类型,进行确定其默认值的
			clone = src && jQuery.isPlainObject(src) ? src : {};
		    }

		    // Never move original objects, clone them
		    //再次调用extend方法,进行深度扩展
		    target[ name ] = jQuery.extend( deep, clone, copy );

		    // Don't bring in undefined values
		} else if ( copy !== undefined ) {
		    //不需要深度copy的话,直接赋值
		    //这里同样没有处理,是否会覆盖原有的属性值
		    target[ name ] = copy;
		}
	    }
	}
    }

    // Return the modified object
    //把获得的结果对象,返回。
    return target;
};


关于源码中出现的isPlainObject方法,可以参考:jQuery源码学习(一)–isPlainObject

源码的东西,也就是上面的这些了,在使用中,有些需要注意的地方。就是如果想要在jQuery对象上进行扩展的话,那么只有两种使用方法,一种用于浅copy,一种用于深copy,它们的写法分别为:


var obj = {};
$.extend(obj);//浅copy
$.extend(true,obj);//深copy


当然啦,实例中扩展,也是相同的情况,这里就不再写一遍了。

但是当使用另外的方法时,就需要我们能明确,最终是被扩展到了哪个对象之上了。

这里的情况,其实可以分为两种。


var a = {aa:"aaa"},
    b = {bb:"bbb"},
    c = {cc:"ccc"},
    d = null;
	
d = jQuery.extend({},a,b,c);   //a,b,c都没有被扩展
console.log(a === d);
//false,表示最后返回的不是a对象

a = {aa:"aaa"}
d = jQuery.extend(a,b,c);   //a,b,c都没有被扩展
console.log(a === d);
//true,表示最后返回的是a对象
//表示,a对象是被扩展之后的目标元素

a = {aa:"aaa"}
d = jQuery.extend(true,a,b,c);   //a,b,c都没有被扩展
console.log(a === d);
//true,表示最后返回的是a对象
//表示,a对象是被扩展之后的目标元素

a = {aa:"aaa"}
d = jQuery.extend(true,{},a,b,c);   //a,b,c都没有被扩展
console.log(a === d);
//false,表示最后返回的不是a对象

//上面这些情况都是正常的,也是我们可以想象的,
//最后返回的,都是传入的参数中,第一个引用类型

//那么,还有另外一种情况,那就是当传入的第一个参数是false时呢?
a = {aa:"aaa"}
d = jQuery.extend(false,{},a,b,c);   //a,b,c都没有被扩展
console.log(a === d);
//false,这个应该算是正常吧。不需要解释


a = {aa:"aaa"}
d = jQuery.extend(false,a,b,c);   //a,b,c都没有被扩展
console.log(a === d);
//false,这个按照上面的规律,本应该是true的,但是这里。。
//原因就在于源码中的:target = arguments[0] || {},
//当第一个参数传入false时,target被初始化为一个空对象
//导致所有的内容都被扩展到这个空对象上面
//这其实不应该算是问题,因为默认的就是浅copy,
//所以当浅copy时,最好还是不要第一个参数传入false的好


总结

总是感觉,在不考虑是否会覆盖原方法的情况下,就直接把原方法进行了覆盖,是一种钛强韧所难的逻辑,或者说太不尊原方法了。

但是,万一我们无意识的的,把一个原有的很重要的方法覆盖了呢?并且,这个实现也很简单啊,只要加一个判断就行了。不明白为什么没有呢。

本文地址:http://www.zhangyunling.com/?p=214

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>