JQuery源码解析-JQuery.extend()方法

extend方法是jQuery中的继承方法,先说一下extend方法的使用,在进行源码解析。

当extend只有一个参数的时候,代表将对象扩展到jQuery的静态方法或实例方法中,如:

    $.extend({
            a: function () {
                alert("a");
            }
        
        })
     $.fn.extend({
            a: function () {
                alert("a");
            }
        })
        $.a();
        $().a();

在上面的代码可以看出不管是jQuery对象还是实例,都可以用extend方法进行继承,在源码中也是调用的同一个方法,之所以可以这么做的原因是因为在源码中,内部绑定时,用到了this。

$.extend的this就是$ 而 $.fn.extend的this是$.fn,也就是代表实例的原型上扩展。

再看一下传入多个参数的情况,当传入多个参数时,如果第一个参数不是bool类型,默认后面的参数的属性都会被添加到一个参数对象上。

如果第一个参数为bool类型且为true,则代表深拷贝,默认为浅拷贝,false。

     var a = {};
        var b = { tom: { age: 14 } }
        $.extend(a, b);
        a.tom.age = 25;
        console.log(a.tom.age); //25
        console.log(b.tom.age);//25

上面的代码的问题可以看到,当继承的对象属性中有引用类型的时候,那么会造成两个两个对象同时指向一个对象,这样如果改变一个的话,另一个也随之改变,所以:

$.extend(true,a, b);

把第一个值给true,进行深拷贝就可以了。

下面看一下extend方法内部的源码。

内部的大体结构如下:

jQuery.extend = jQuery.fn.extend = function() {
//定义一些参数
if(){}    //看是不是深拷贝的情况。
if(){}    //看参数是否正确
if(){}    //看是不是插件的情况
for(){     //处理多个对象参数
    if(){}             //防止循环调用
    if(){}            //深拷贝
    else if(){}     //浅拷贝
}
}        

第一部分定义一些参数:

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

然后进行判断,看是否第一个参数传入的是bool值,如果是,则将其赋值给deep,然后将target赋值为第二个参数。

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

然后进行判断,看target是否为对象或函数,如果非对象,如字符串等,则将其赋值为空对象。

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

然后判断是否为扩展工具方法,如果是的话,则直接将target赋值为this

// 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;

首先利用一个for循环来处理多个参数的情况,接着判断当前参数是否为null,如果为null的话,就不向下执行了。

再接着是一个for循环,循环传入的参数,在这个循环里进行对当前这个参数的对象进行解析和扩展。

首先对src和copy进行赋值

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;
                }

第一个判断,deep为true,也就是第一个参数为true,并且复制的对象不为空,还必须是对象或者数组,才可以进行深拷贝。

接下来是对是否为数组进行判断,最重要的一句是

 clone = src && jQuery.isPlainObject(src) ? src : {};

这里先判断src是否为空,如果不为空则把target[name]赋值到clone上,如果为空则传入一个空对象,这里是为了处理这种情况。

     var a = { tom: { sex: "man" }};
        var b = { tom: { age: 14 } }
        $.extend(true,a, b);
        console.log(a);

当扩展对象和参数都有一个共同的对象时,那么正确做法是把参数b中不同的属性附加到a中,而不是进行覆盖。

所以这里需要进行判断。

如果这里将源码改了,也就是将:

clone = src && jQuery.isPlainObject(src) ? src : {};  

替换为

clone =  {};

只传入一个空对象,那么在次执行的结果为:

可以看到,这里就是将两者的相同属性进行了覆盖操作,这样是不对的。

最后进行递归调用,当深拷贝的时候,因为无法确定有几层,所以需要进行递归,直到最后一层。再次调用这个方法:

    // 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;
                }

最后返回target。

// Return the modified object
    return target;

extend的方法就结束了。

原文地址:https://www.cnblogs.com/y8932809/p/5863764.html