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