jQuery源码分析5-extend()方法
jQuery1.8.3中289行到353行代码,主要讲jQuery中的extend()方法的实现。
一、jQuery原型链
前面也讲解到了,每当我们调用$()时,我们会new一个jQuery的实例,而这个构造函数就是jQuery对象原型上的init事件。但是当我们对这个实例取jQuery原型上的方法时,却仍是可以取得到的,就像,
$('').jquery; // '1.8.3'
那jQuery是如何实现的呢?直接看189行。
jQuery.fn.init.prototype = jQuery.fn;
可以发现,此处将jQuery的原型赋值给了init的原型,因此构造出来的实例是可以直接访问jQuery原型上的属性和方法的。
二、extend()方法
1.常用方法
先了解下extend的方法,将会更容易明白extend的源码。 我们可以使用extend方法给jQuery扩展常用方法。我们也可以使用extend实现深浅拷贝,其实这儿的扩展,就是使用的拷贝的方式。
2.扩展方法流程
第一行进行了两次赋值,jQuery.extend = jQuery.fn.extend,因此jQuery的工具方法和实例方法是同时支持这个方法的,且实现方式是一样的。 当我们要扩展jQuery对象时,是这么写的,
jQuery.extend({
// ...
});
此时是只有一个参数的。所以target就是这个扩展的对象,那么if ( typeof target === "boolean" )和if ( typeof target !== "object" && !jQuery.isFunction(target) )这两个if就不会进入。而由于只有一个参数,所以length是1,符合第三个if的条件,于是i变成了0,而target也被重新赋了值,指向了this。
紧接着是一个for循环,循环传入的arguments参数。然后内层有嵌套了一个for循环,循环该参数下的所有属性。用src表示复制到的对象(jQuery)下的对应的值,copy表示被复制的对象下的值,
if ( target === copy ) {
continue;
}
该地方特地判断了target是否等于copy,是为了防止后面递归时照成的无限嵌套,这种特殊情况是:
var obj = {};
var obj1 = {
b: obj
}
$.extend(obj, obj1); // 注释掉continue,控制台下看,是一个嵌套十分深的对象,此处添加这个条件就是防止这种问题。
由于extend默认是浅拷贝的,扩展对象时也是,所以接下来会走到else中,于是就直接给target(此时是jQuery)下增加个对应的属性,并赋上值。
3.浅拷贝
由于extend默认是浅拷贝的,所以我们常常可以想前面那样写,当然也可以$.extend(false, obj, obj1)这种写法。当然,像$.extend({}, {a:1},{b:2} /..../),这样也是兼容的,会把后面所有的对象的值复制到第一个对象上去。$.extend(false, obj, obj1)这种写法放到深拷贝一起讲。
由于第一个参数是被复制到的对象,所以target的值仍然是个object,所以前两个if也不走,而由于length不可能是1,所以也不会走,剩下的就是for循环的复制了,与扩展方法的流程一致。
4.深拷贝
由于第一个参数一定是个布尔值,此时deep值会被赋值,记录了此次拷贝是深还是浅。因为第一个参数仅仅是布尔值,并非我们所要的数据,因此target需要取后一个参数,该对象是被赋值到的对象,而i也被置为了2。
走到第二个if,if ( typeof target !== "object" && !jQuery.isFunction(target) ),这个if也是不会进入的,他的作用只是处理由于输入参数有问题,导致的拷贝错误,他会将target转成一个空对象,这里也特别过滤了function这种类型。
由于参数的个数都是大于等于3的,所以if ( length === i )同样不会进入。最后走到了两层嵌套的for循环,当deep时true,也就是深拷贝时,且满足被复制的值是一个对象或是数组时,会再次调用extend方法,进入递归。而在递归之前,jQuery对复制到的数据进行了一次处理。当他是数组时,就是用原先的数组,若不是,则新建个数组,而对象也是同样的处理。这里的特殊处理是为了应对一下问题的:
var obj = {
a: {
hello: 1
}
};
var obj1 = {
a: {
world: 2
}
}
console.log($.extend(true, obj, obj1));
修改jQuery中的源码,
//...
clone = \[\]; //src && jQuery.isArray(src) ? src : \[\];
//...
clone = {}; //src && jQuery.isPlainObject(src) ? src : {};
//...
我们发现在这种情况下,因为obj和obj1都有a这个属性,由于没有使用obj中的对象,所以hello:1这个值就会丢失。最后a属性下只有world:2这种情况。
进入递归后,如果复制的变量是数组或对象的话,继续递归,直到非对象和数组时,进行赋值。
如果deep直接被赋值了false的话,流程和默认情况下的拷贝是一样的。 这样,extend方法就实现了。