分析JQuery中的'extend'

333 阅读4分钟

写在最前面


之前看了阮大佬的博客并且自己分析了一波,感觉意犹未尽,于是趁热自己抽时间又看了下Jquery中的extend方法源码,然后写写自己的理解。

预备工作


在看这个方法之前,我先看了下它的使用方法(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;
}

长还是挺长的,咱们已经分好段了,接下来一段段的看就好了

一脸懵逼


    var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
        i = 1,
        length = arguments.length,
        deep = false;

一般来说,开头都是进行初始化什么的,jq也不例外,第一句除了target = arguments[0] || {}以外,都是提前对变量进行声明,这是个好习惯!

target = arguments[0] || {}做了啥?我们知道或运算符 || 会进行判断,如果存在arguments[0]就将其值赋值给target,如果没有就将target声明成一个空对象。

二脸懵逼


    // Handle a deep copy situation
    if (typeof target === "boolean") {
        deep = target;
        target = arguments[1] || {};
        // skip the boolean and the target
        i = 2;
    }

先看看英文注释,用于深拷贝的预处理。从前面我们知道,extend有两种使用方法,不记得在回去看看哟。。第二种就是第一个参数为boolean,标志着深拷贝。

进入这一步之前target是extend的第一个参数,对target进行简单的类型判断,如果是Boolean类型,说明是使用的第二种方法。

  • 首先我们用deep将这个boolean标志存下来
  • 那么target此时肯定不能指向第一个参数,于是将target指向第二个参数:target = arguments[1] || {}
  • 这里将i = 2,i是啥?现在还不知道它的作用,先不管。

三脸懵逼


    // Handle case when target is a string or something (possible in deep copy)
    if (typeof target !== "object" && !jQuery.isFunction(target)) {
        target = {};
    }

到了这一步,我们知道此时target指向第一个参数,也就是我们目标对象。等等...我们现在只知道指向的是arguments[1]并没有告诉我们这是对象,于是这里就是进一步对target的身份进行核实,你是对象这里就不会走,不是对象,抱歉!你需要变成一个对象。。

四脸懵逼


    // extend jQuery itself if only one argument is passed
    if (length === i) {
        target = this;
        --i;
    }
  • 如果是第一种$.extend(obj1, obj2, ...)的话,length = 2... 这里i = 1
  • 如果是第二种$.extend(boolean, obj1, obj2)的话,length = 3... 这里i = 2

这样看来并不会存在length === i 的这种情况,jq写错了吗?其实并没有。。如果你仔细的浏览了下文档机会发现这样的一句话

也就是说参数个数为1的时候,这个if判断是成立的,将this传给了target,这个时候我们的目标对象就是jq本身。所以结合文档看源码还是挺有用的,不然真的会云里雾里的。

在这里--i之后,i的值变成0。(extend参数个数为1的情况下)

到这里,我们有个大胆的想法,这个i会不会就是target之后的对象的下标位置呢?

例如:

$.extend(obj1, obj2, obj3, obj4)
target = arguments[0]
length = 4
i = 1  //obj2的下标
$.extend(true, obj1, obj2, obj3, obj4)
target = arguments[1]
length = 5
i = 2  //obj2的下标
$extend(obj1)
target = this  //jq本身
length = 1
i = 0  //obj1的下标

在三种情况下,我们得到不同的三个i,到这里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;
                }
            }
        }
    }

这一大段无非就是个拷贝嘛。。先别害怕。这么一大段for循环,就是对target后面参数对象进行遍历,分别将每个参数对象中的属性,拷贝到target对象中。知道了这个咱们接着看下去。

  • 首先进行了if判断(options = arguments[i]) != null这个参数是不是null,为null就跳过继续下一个。
  • 不为null就对该参数对象中的每个属性进行拷贝,这里有一点要提一下
// Prevent never-ending loop
    if (target === copy) {
         continue;
    }

这里主要是担心参数对象的属性就是target,例如

obj2 = {'obj1': obj1}
$.extend(obj1, obj2) 

在这种情况下,会陷入死循环,这个地方,理解一下就懂了,操作的是同一个内存(对象)。

除了这一点,下面一大段就是进行类型判断然后进行赋值。配合我上一篇文章:JavaScript面向对象之三(非构造函数继承)中的深拷贝继承一起食用更佳。

还懵逼吗?


看了源码,最大的感受就是,这种经典的库,考虑真的很周全很仔细,尤其是那块死循环的判断,刚开始没理解,最后恍然大悟,理解完浑身畅快的感觉真的很棒。喝口水,切图去...哈哈。