昨天在研究继续研究JS插件的时候注意到了extend这个函数,然后就用几个示例去加深对它的印象,不过在过程中对于deep这个参数产生了疑惑。当jq1.1.4版本后extend这个函数增加了一个参数deep,用来进行选择是否深拷贝,深浅拷贝这里我就不说了,这里直接用例子来看一下
给出3个例子,大家应该就明白了:
1.我们可以发现当我们去改变a中的age值的时候,b中改变
var a = {tom: { age: 15 }};
var b = { tom: { age: 14 } }
extend(a, b);
a.tom.age = 25;
console.log(a.tom.age); //25
console.log(b.tom.age);//25
2.我们可以发现当我们去改变a中的age值的时候,b中没有改变
var a = {tom: { age: 15 }};
var b = { tom: { age: 14 } }
extend(true,a, b);
a.tom.age = 25;
console.log(a.tom.age); //25
console.log(b.tom.age);//14
3.我们可以发现当我们去改变a中的age值的时候,b中没有改变
var a = {tom: { age: 15 }};
var b = { tom: { age: 14 } }
extend(false,a, b);
a.tom.age = 25;
console.log(a.tom.age); //25
console.log(b.tom.age);//14
我们可以发现3个例子的区别就是第一个参数deep是否存在和为true和false,而且第一个例子是浅拷贝,第二个和第三个都是深拷贝
这里可以有2个结论:
1.这个函数默认就是浅拷贝
2.传true和false都是深拷贝
一开始对于第2个结论很不理解,讲道理true为深拷贝,那么false肯定为浅拷贝呀,为什么都一样呢?在网上查了很多资料,发现还是不明白,后来去官网查了一下知道(证明以后我们查东西还是去官网比较靠谱,网上很多博客都是断章取义),第一个参数不能为false。https://api.jquery.com/jquery.extend/ (可以去看中文的文档,也可以用chrome浏览器翻译)
Warning: Passingfalse
for the first argument is not supported.
原来不能传false,之所以这样是我们一直都是惯性思维,理解true为深,那么false肯定为浅咯~ -。-(惯性思维害死人,我还一直纠结为什么会这样)
其实到这里我们都是知其然,不知其所以然,在纠结这个问题的过程中,我查了源码,终于知道其所以然了。
target = arguments [ 0 ] | | { }
//就是这一句话~(大家可以定位到这句话),它是把这个函数的第一个参数传递给target,最重要的是这里用了一个 | | 或,如果当我第一个是假的话就直接赋值一个空对象给target,这样就可以理解,为什么我们可以传true不能传false了,当我们传true的时候为真,它就直接把true给它了,当我传false的时候为假,它就会把一个空对象给target。
因为上面这个条件,我们就可以解释为什么当进行后面那个判断时,我传false会进不去的原因了,就是因为typeof (target)为object!!!!!
if ( typeof (target) === "boolean" ) {//如果第一个参数是boolean类型,则将该参数赋给deep,即是否深拷贝
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
然后再看一个例子:
var empty={};
var a = {tom: { age: 15 }};
var b = { tom: { age: 14 } }
extend(empty,a,b);
empty.tom.age = 25;
console.log(empty.tom.age);//25
console.log(a.tom.age); //15
console.log(b.tom.age);//25
我们可以发现,当我们去浅拷贝的时候,它只是会影响到最后一个对象的参数,这个也可以理解,因为后面的参数会覆盖前面的,所以浅拷贝的时候,最后被合并的对象(empty)引用的是最后一个参数对象( b )的属性。
所以当我们对插件参数进行覆盖的时候最好用这种方式:
第一个参数为true,第二个参数为空对象,就是为了不让其他对象的值被覆盖掉。
var empty={};
var a = {tom: { age: 15 }};
var b = { tom: { age: 14 } }
empty=extend(true,{},a,b);
empty.tom.age = 25;
console.log(empty.tom.age);//25
console.log(a.tom.age); //15
console.log(b.tom.age);//14
最后附上别人对这个函数的源码解析:
jQuery.extend = jQuery.fn.extend = function() {
/*
传入的对象分为扩展对象和被扩展对象
*/
var options, name, src, copy, copyIsArray, clone, //
target = arguments[0] || {}, //被扩展的对象
i = 1, //设置扩展对象的起始值,默认从第二项开始
length = arguments.length, //传递参数的个数,以便下面循环扩展对象使用
deep = false; //默认浅复制
/*
处理深层拷贝或浅拷贝情况
extend(Boolean,src1,src2..srcN);
*/
if ( typeof target === "boolean" ) {
deep = target; //将deep设为target,此时target为传进来的Boolean值,true or false;
target = arguments[ i ] || {}; //重新设置被扩展对象,为参数的第二项
i++; //重设扩展对象的起始值,从第三项开始
}
/*
被扩展的不是对象或函数,可能是String,Number或其他;
extend("",src1,src2...srcN);
*/
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {}; //重新设置target的值为空对象
}
/*
当只传入一个对象
extend(src1);
将target设为jQuery对象或者jQuery.prototype,来扩展jQuery静态属性方法或是实例属性方法
$.extend(src1); //扩展jQuery对象
$.fn.extend(src1) //扩展jQuery.prototype
*/
if ( i === length ) {
target = this;
i--; //重设扩展对象起始值,从第0个开始
}
/*
被扩展对象和扩展对象所有情况处理完毕,开始循环进行拷贝
对从i开始的多个参数进行遍历
*/
for ( ; i < length; i++ ) {
if ( (options = arguments[ i ]) != null ) { //只处理有定义扩展对象
//扩展基本对象
for ( name in options ) { //循环每一项扩展对象
src = target[ name ];
copy = options[ name ];
// 防止循环引用,window === window.window.window
if ( target === copy ) {
continue;
}
/*
对象或数组做深拷贝
deep:判断是否要深拷贝
copy:保证copy存在
jQuery.isPlainObject:判断copy是否是一个纯粹的对象,通过{} 或 new Object 创建
jQuery.isArray:判断是否为数组
*/
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
//为数组
if ( copyIsArray ) {
copyIsArray = false; //设为false,以便下次再重新判断是否为数组
clone = src && jQuery.isArray(src) ? src : []; //设置clone为一个数组
} else {
clone = src && jQuery.isPlainObject(src) ? src : {}; //设置clone为一个对象
}
//递归深度拷贝
target[ name ] = jQuery.extend( deep, clone, copy );
//过滤未定义的值
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
//返回修改后的对象
return target;
};