提起JS中的继承很多”大神“们都会提起call,apply,单纯的对象赋值继承,以及原型链继承等众多的方式以及它们的不足之处,而且还会不时的把一些面向对象的设计模式”团团“的带出来,可谓是厉害非常啊!而当被问道JQuery的extend却很少人真正去实际了解它的”秘密“。其实JQuery的继承方式就是一种拷贝方式的继承,但是在这里还有点小小的“猫腻”需要大家了解下。那么马上看这段代码:
var a1 = { prototype1: { i: "1" } }; var b1 = { prototype1: { j: 1 } }; jQuery.extend(a1, b1); console.log(a1); console.log(b1);
那么结果并不是我们希望看到的,但我们知道JQuery的extend是第二个参数对象合并到第一个对象中去的,如果要是能合并进去并且不会将原对象的子对象属性覆盖掉那就简直是perfect。
吼吼,答案是一定的。仅仅是因为我们上面代码$.extend(a,b);在JQuery内部走了一个错误的else分支,这个分支是这样的。JQuery2.1.4版代231行代码如下:
} else if ( copy !== undefined ) { target[ name ] = copy; }
这行就是导致了a1的子对象i属性被覆盖的原因,因为这个分支并没有去判断当前对象是否仍然包含对象,这样我们就会马上有兴趣看看它的if()是咋个样纸。
在JQuery2.1.4版代177行至202行代码如下:
target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; // Skip the boolean and the target target = arguments[ i ] || {}; i++; } if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } if ( i === length ) { target = this; i--; }
在这里提一嘴这个deep变量的含义就是深拷贝的意思,JQuery的注释也可以帮助你这样理解,吼吼,我幸运的看到了这个单词,要不然真不知道怎样叫它。
由上边的代码我们可以发现它还有一个参数,那就是在extend参数列表的first位置,在分支中我们可以清楚的看到deep默认值为false,当我们将例子中代码改为如下样子:
$.extend(true,a1,b1);
这样就使用到了,注释中所谓的深拷贝,运行后我发现console.log(a)结果就变成了:{ prototype1: { i: "1", j: 1 } };这就变成了我们理想中的样子,那么
target[ name ] = copy;
这句代码我们就更好理解了,就是针对简单键值对才适用的!而且在以上的几个if中,已经限定了传进去的目标(第一或第二个参数位置)一定要是对象,如果不传,那就是length长度与i相等,就会合并到$对象中去,JQuery的$成为目标对象。
但深拷贝又是怎样把每个对象都得到并合并的呢?我们继续看下边的代码:
for ( ; i < length; i++ ) { if ( (options = arguments[ i ]) != null ) { for ( name in options ) { src = target[ name ]; copy = options[ name ]; if ( target === copy ) { continue; } 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 : {}; } target[ name ] = jQuery.extend( deep, clone, copy );
从这句 for ( ; i < length; i++ ) 中我们马上就能够看出这个extend还是个拥有无限参数列表的家伙,可以随你的心意去合并众多对象(详情见Jquery 2.1.4版181行)。嘿嘿不用看了,你懂的。。。
让我们把目光转向option和下边较长的if中我们就能够明白了,如果目标对象要是复杂的就够就会拿src进行合并,而不会去new一个新的JS对象,这样就不会将原来有的覆盖掉。
而且会递归的遍历到最深一层,直到没有子对象,那就会继续执行如下代码
} else if ( copy !== undefined ) { target[ name ] = copy; }
这样递归返回就不会丢失任何的子对象了。
然而话说到这里还有一个地方没有解释,就是if ( target === copy ) 这个判断项。一旦子对象的属性名要是和父级的对象重名的话,那么马上就会跳出这个判断,以免造成“死递归”。
解释到这里JQuery实现的extend继承是不是也是不亚于那些平日里貌似很“牛逼”的各种对象继承呢?希望广大童鞋们也能够活学活用哈。
还是那句话有什么问题请指出,小弟不胜感激啊。么么哒!