jQuery版本 1.7.1
由于jQuery版本众多,这里只研究1.7.1版本的extend的实现方法
代码
extend方法介绍
extend方法在jQuery中有较多的应用,平时写代码时也会用到,主要是利用其提供的deep copy的功能
以下是jQuery中的代码详情
点击查看代码详情
jQuery.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;
};
参数分析
target = arguments[0] || {}
表示赋值运算符的左操作数,也就是被赋值对象i=1
表示赋值运算符的右操作数,也就是赋值对象,由于支持将多个对象赋值到同一个左操作数上,因此通过i
表示当前正在赋值的索引length=arguments.length
函数参数的个数 由于JS中对于函数签名没有严格的限制,因此,不能通过提前定义函数参数类型来限制传入参数的类型,以及限制函数传入参数的个数,因此有了以下修正函数参数的方法:typeof target === "boolean"
也就是说jQuery.extend(true,...)这种方式进行调用时,传入true表示进行deep copy,进行target
赋值左操作数和i
赋值右操作数的修正
实现方式
- 首先通过一个
for
循环来实现多个赋值右操作数的赋值操作 if((options = arguments[i])!=null)
这里使用了变量类型自动转换的特性,过滤null和undefined对象- 使用强制相等符号进行判断,防止循环赋值,如果没有这个判断,有以下两种情况
- 做了一个实验,在deep copy模式下能够正常拷贝
输出:var a = {a:1}; var b = {b: a}; var _b = $.extend(true, a, b);
原因:分析代码可以看出,由于是deep copy的原因,{a:1, b:{a:1}}
jQuery.extend(...)
的返回值已经与target不是同一个对象了,因此不会出现这种循环赋值的情况了- 在非deep copy模式下,会出现嵌套很深的情况
输出:var a = {a:1}; var b = {b: a}; var _b = $.extend(a, b);
{a:1, b:{a:1, b:{a:1, b:{a:1, b....}...}
- 对于非null和undefined对象,遍历对象属性,将
options[i]
对象的属性值copy赋值到target对象对应的属性值上- 对于非deep copy, 过滤undefined属性值,然后进行赋值
target[name]=options[name]
- 对于deep copy
- 会通过
if(...©&..)
过滤属性值可以转换为false的属性- 这里比较有意思的一个点,不知道是不是隐藏的bug,还是别有用意,就是当属性值为null时,不会进行deep copy操作,但是由于
copy!==undefined
是一个强类型的判断,null!==undefined
为true,因此会进行属性值的赋值
- 这里比较有意思的一个点,不知道是不是隐藏的bug,还是别有用意,就是当属性值为null时,不会进行deep copy操作,但是由于
- 判断赋值右操作数的类型,以保证赋值后左操作数属性值的类型和右操作数属性值相同,这里对右操作数为对象和数组进行分别进行选择,当左操作数属性值和右操作数属性值类型不同时,会强制重新赋值一个空的对象或数组给左操作数
- 通过递归来进行属性的递归赋值实现deep copy
- 会通过
- 对于非deep copy, 过滤undefined属性值,然后进行赋值
- 赋值结束后,返回左操作数
实战
属性值为null或者undefined时
var b = {a:undefined, b:null, c:1};
var _b = $.extend(true, {}, b);
输出:
{b: null, c: 1}
可见,虽然undefined属性值不会进行赋值,但是null属性值会进行赋值
左操作数属性值类型和右操作数不同
var b = {a:[1,2,3]};
var a = {a:{1:10,2:20,3:30,4:40}};
var _b = $.extend(true, a, b);
输出:
{a:[1,2,3]}
左操作数属性值类型和右操作数相同
var b = {a:[1,2,3]};
var a = {a:[10,20,30,40]};
var _b = $.extend(true, a, b);
输出:
{a:[1,2,3,40]}
总结
了解了extend方法的内部逻辑,用起来就更得心应手了;也可以发现即使是这么广泛使用的库中,代码逻辑也是有可以改进的地方,比如循环拷贝的判断,是否应该只针对非deep copy的情况