Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

分析JQuery中的'extend' #22

Open
Pomelo1213 opened this issue Apr 18, 2018 · 0 comments
Open

分析JQuery中的'extend' #22

Pomelo1213 opened this issue Apr 18, 2018 · 0 comments
Labels
JavaScript JavaScript

Comments

@Pomelo1213
Copy link
Owner

写在最前面


之前看了阮大佬的博客并且自己分析了一波,感觉意犹未尽,于是趁热自己抽时间又看了下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面向对象之三(非构造函数继承)中的深拷贝继承一起食用更佳。

还懵逼吗?


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

@Pomelo1213 Pomelo1213 added the JavaScript JavaScript label Aug 21, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JavaScript JavaScript
Projects
None yet
Development

No branches or pull requests

1 participant