jquery对象扩展方法$.extend 方法源码解析
2017-03-04 02:54

jquery的extend方法

jquery提供了两个对象扩展方法,jQuery.extend(object)和jQuery.fn.extend(object)。

@1,jQuery.extend :用来扩展jQuery对象本身,用来在jQuery的命名空间上增加新函数。

jQuery.extend({
  min: function(a, b) { return a < b ? a : b; },
  max: function(a, b) { return a > b ? a : b; }
});
jQuery.min(2,3); // => 2
jQuery.max(4,5); // => 5

@2,jQuery.fn.extend:用来扩展jQuery元素集来提供新方法(通常用来制作插件)

jQuery.fn.extend({
  check: function() {
    return this.each(function() { this.checked = true; });
  },
  uncheck: function() {
    return this.each(function() { this.checked = false; });
  }
});
$("input[type=checkbox]").check();
$("input[type=radio]").uncheck();

jQuery.extend用来扩展jquery的静态方法,而jQuery.fn.extend用来扩展通过jquery获取的元素的集合的方法。

举个例子,

通过jQuery.extend扩展的min,max方法,在$("input[type='checkbox']")得到的对象是无法调用的。

jQuery的extend方法源码

jQuery.extend = jQuery.fn.extend = function() {
	var options, name, src, copy, copyIsArray, clone,
		target = arguments[0] || {},
		i = 1,
		length = arguments.length,
		deep = false;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		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) ) {
		target = {};
	}

	// extend jQuery itself if only one argument is passed
	if ( length === i ) {
		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 ) {
					continue;
				}

				// Recurse if we're merging plain objects or arrays
				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
					if ( copyIsArray ) {
						copyIsArray = false;
						clone = src && jQuery.isArray(src) ? src : [];

					} else {
						clone = src && jQuery.isPlainObject(src) ? src : {};
					}

					// Never move original objects, clone them
					target[ name ] = jQuery.extend( deep, clone, copy );

				// Don't bring in undefined values
				} else if ( copy !== undefined ) {
					target[ name ] = copy;
				}
			}
		}
	}

	// Return the modified object
	return target;
};

上面是jquery-1.8.3版本中对extend方法的定义。

jQuery.extend = jQuery.fn.extend = function() {

    ....
    ......
}

从这个方法的定义可以看出,jQuery.extend和jQuery.fn.extend指向的是同一个方法。

jQuery.extend = jQuery.fn.extend = function() {
	var options, name, src, copy, copyIsArray, clone,
		target = arguments[0] || {},
		i = 1,
		length = arguments.length,
		deep = false;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		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) ) {
		target = {};
	}

	// extend jQuery itself if only one argument is passed
	if ( length === i ) {
		target = this;
		--i;
	}

	for ( ; i < length; i++ ) {
	
	    ....
	    //对象拷贝代码
	}
    return target;
}

我们先把对象拷贝的代码简化,开外层逻辑。

@1:定义返回的目标target:

target = arguments[0] || {},

在给extend方法传入的参数不为空时,初始定义target为为第一个参数,为空时为一个新建的空对象。

@2:定义对象拷贝的开始参数:

i = 1,

@3:定义是否是深度拷贝,默认为false

deep = false;

@4:获取参数的个数:

length = arguments.length,

@3:先判断target的类型是否为布尔型。

if ( typeof target === "boolean" ) {
		deep = target;
		target = arguments[1] || {};
		// skip the boolean and the target
		i = 2;
	}

如果target为布尔类型,那么将target作为是否深度拷贝的deep参数的值,并且将第二个参数作为拷贝的target目标,“i=2”是让下面的拷贝过程跳过布尔型和target,从第三个参数开始拷贝。

@4:判断target是否是object或function,(js中object和function都是对象,object包含数组)。

if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
		target = {};
	}

如果target不是对象,而是string字符串或者其他整型等数据类型,则将target定义为空对象。因为extend方法是将多个对象的属性拷贝到第一个对象上这么设计的,所以这里“i”还是"1",不用动。

@5:判断extend的参数个数是否只有一个

if ( length === i ) {
		target = this;
		--i;
	}

如果extend方法接收的参数只有一个,那么第一个参数就不作为target,而是将this作为被扩展的target。通过jQuery.extend调用,则this为jQuery;通过jQuery.fn.extend方法调用,则为包装DOM元素的jQuery对象(实际是jQuery.prototype.init方法对象,jquery框架的布局和以前我讲过的插件写法一样,可以参照js插件写法)。因为默认的 "i=1",所以这里要“--i”,将 i 变为 0。至于为什么用"--i",而不是用"i-=1",这是涉及到js运行性能的问题,“--i”是自增减算法,而"i-=1"是数学运算算法,使用的变来那个寄存器要多一个,性能自然要慢。详细原理可以自行百度。

所以你如果是像要给jQuery对象或者jQuery包装对象扩展方法功能,那么一定要传递一个参数。而你如果是要给自己的对象扩展属性或方法,那么第一个是目标对象,后面的2-n个对象是扩展对象,至少要传两个参数。

@6:for循环给目标对象扩展属性或方法。

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 ) {
					continue;
				}

				// Recurse if we're merging plain objects or arrays
				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
					if ( copyIsArray ) {
						copyIsArray = false;
						clone = src && jQuery.isArray(src) ? src : [];

					} else {
						clone = src && jQuery.isPlainObject(src) ? src : {};
					}

					// Never move original objects, clone them
					target[ name ] = jQuery.extend( deep, clone, copy );

				// Don't bring in undefined values
				} else if ( copy !== undefined ) {
					target[ name ] = copy;
				}
			}
		}
	}


这里算是extend扩展的主要代码。

if ( (options = arguments[ i ]) != null ) {
    ...
}

用options变量来引用扩展对象,同时判断不为null。

for ( name in options ) {
    ...
}

对象属性遍历。

src = target[ name ];
copy = options[ name ];

// Prevent never-ending loop
if ( target === copy ) {
    continue;
}

获取扩展对象和目标对象的属性值,如果两个值相等,则跳过赋值扩展。

if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
    
}

“isPlainObject”是否是{key:value,key:value}形式的json格式对象。"isArray"判断是否是数组。deep是深度拷贝,

在深度拷贝模式下:

if ( copyIsArray ) {
	copyIsArray = false;
	clone = src && jQuery.isArray(src) ? src : [];

} else {
	clone = src && jQuery.isPlainObject(src) ? src : {};
}

// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );

如果扩展对象是数组,那么src为数组时,则是在src数组中增添元素,否则将src定义为空数组。类似的,不是数组时,src为对象,则在src对象上扩展属性,如果不是对象,则定义为空对象。

target[ name ] = jQuery.extend( deep, clone, copy );

给target目标对象的对象属性赋值,这个复制是递归调用jQuery.extend,它传入了三个参数,deep表示深度扩展,clone为副本目标对象,copy为扩展对象的属性。这个递归可以处理类似下面结果的完整扩展:

{
    user:{
            name:'girl',
            body:{
                    height:165,
                    face:'beatiful',
                    threeD:{
                            xiong:'D',
                            yao:'xi',
                            tuo:'yuan'
                           }
                }
        }
}

这类扩展对象使用深度复制可以完整的扩展对象,如果不使用深度复制,那么目标对象的内层值只是原对象的引用,原对象改变,目标对象的内层值获得的也是改变后的值。如果是数组的话,无论有没有传递deep值,都是采用深度扩展。而如果你的扩展对象的属性值都是基本类型的变量,而你仍然使用的深度扩展,那么由于不符合“isPlainObject"的判断结果,就会走下面的非深度扩展代码:

else if ( copy !== undefined ) {
   target[ name ] = copy;
}

非深度扩展是直接将扩展对象的属性值赋值给target对应的属性上。而使用"target[name]"这种访问形式,如果对象有这么个属性,就会覆盖其值,如果对象没有此属性,那么就会添加一个属性再赋值。

extend对象扩展总结

jquery的extend方法是逻辑很完善的带有深度扩展的一个方法。而我们平时使用,很少用到这么复杂的逻辑,即使在写js插件时,对于插件参数对象的扩展,我们一般也就是一层扩展而已。简化的exend方法代码如下:

var myExtend=function(){
    var target = arguments[0] || {},
        i = 1,
	length = arguments.length,
	options, name, src, copy;
	for ( ; i < length; i++ ) {
	    if ( (options = arguments[ i ]) != null ) {
	        for ( name in options ) {
		   src = target[ name ];
		   copy = options[ name ];
		   if ( target === copy ) {
			    continue;
		   }
		   target[ name ] = copy;
	    }
	}
   return target;
}

这种简化的extend方法就需要传递两个以上的对象参数,第一个是目标对象,后面的都是扩展对象。

var sets = myExtend({},opt1,opt2);

看完这篇对于extend源码的解析和简化处理,希望能帮助你理解对象扩展的原理,能够更好的使用extend扩展方法,或者自己定义简化的extend方法供自己的插件库使用。

原创文章,转载请注明来自:妹纸前端-www.webfront-js.com.
阅读(2482)
辛苦了,打赏喝个咖啡
微信
支付宝
妹纸前端
妹纸前端工作室 | 文章不断更新中
京ICP备16005385号-1