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的方法就結束了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM