jQuery源码分析5-extend()方法

2017年07月23日Web前端

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方法就实现了。