這兩個方法用的是相同的程式碼,一個用來給jQuery物件或一般物件合併屬性和方法一個是針對jQuery物件的實例,對於基本用法舉幾個例子:
html程式碼如下:
下面寫js裡面的用法:
合併兩個普通物件
//給予兩個普通物件合併屬性
var obj1={name:'Tom',age:22};
var obj2={name:'Jack',height:180};
console.log($.extend(obj1,obj2)); //Object {name: "Jack", age: 22, height: 180}
為jQuery物件新增屬性或方法
$.extend({hehe:function(){alert('hehe');}});
$.hehe(); //alert('hehe')
這個用法很重要,是jQuery內部添加實例屬性和方法以及原型屬性和方法的實現方法也是編寫jQuery插件的方法,下面是jQuery1.7.1中使用extend方法擴展自己的方法和屬性
jQuery.extend({
noConflict: function( deep ) {
if ( window.$ === jQuery ) {
window.$ = _$;
}
if ( deep && window.jQuery === jQuery ) {
window.jQuery = _jQuery;
}
return jQuery;
},
// Is the DOM ready to be used? Set to true once it occurs.
isReady: false,
// A counter to track how many items to wait for before
// the ready event fires. See #6781
readyWait: 1,
.....
這個例子只傳入了一個物件參數,那麼預設就把this當做待合併修改的物件
為jQuery物件實例新增屬性或方法
//針對jQuery實例擴充合併
console.log($('img').extend({'title':'img'}));//[img, img#img.img, prevObject: jQuery.fn.jQuery.init[1], context : document, selector: "img", title: "img", constructor: function…]
只合併不修改待合併物件
var obj1={name:'Tom',age:22};
var obj2={name:'Jack',height:180};
console.log($.extend(obj1,obj2)); //Object {name: "Jack", age: 22, height: 180}
console.log(obj1); //Object {name: "Jack", age: 22, height: 180}
預設情況下,待合併物件跟回傳結果一樣是被修改了的,如果僅僅想得到一個合併後的物件又不想破壞任何一個原來的物件可以使用此方法
var obj1={name:'Tom',age:22};
var obj2={name:'Jack',height:180};
var empty={};
console.log($.extend(empty,obj1,obj2)); //Object {name: "Jack", age: 22, height: 180}
console.log(obj1); //Object {name: "Tom", age: 22}
使用則遞歸合併或稱為深度拷貝
var obj1={name:'Tom',love:{drink:'milk',eat:'bread'}};
var obj2={name:'Jack',love:{drink:'water',sport:'football'}};
console.log(($.extend(false,obj1,obj2)).love); //Object {drink: "water", sport: "football"}
console.log(($.extend(true,obj1,obj2)).love); //Object {drink: "water", eat: "bread", sport: "football"}
詳細的使用方法可以看參考手冊http://www.w3cschool.cc/manual/jquery/
下面來分析下1.7.1源碼中是怎麼實現的:
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
...
}
首先是定義了一組變量,因為參數個數不確定所以就直接呼叫arguments物件存取傳遞的參數
變數 options:指向某個來源物件。
變數 name:表示某個來源物件的某個屬性名稱。
變數 src:表示目標物件的某個屬性的原始值。
變數 copy:表示某個來源物件的某個屬性的值。
變數 copyIsArray:指示變數 copy 是否為陣列。
變數 clone:表示深度複製時原始值的修正值。
變數 target:指向目標物件。
變數 i:表示來源物件的起始下標。
變數 length:表示參數的個數,用來修正變數 target。
變數 deep:指示是否執行深度複製,預設為 false。
為了更好地了解程式碼實現這裡以上面舉的一個例子作為演示觀察源代碼執行情況
var obj1={name:'Tom',love:{drink:'milk',eat:'bread'}};
var obj2={name:'Jack',love:{drink:'water',sport:'football'}};
$.extend(true,obj1,obj2)
源碼分析
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
判斷是不是深度複製,如果第一個參數是布林值那麼就把第一個參數的值給deep,然後把第二個參數當作目標對象,如果第二個參數不存在就賦值為一個空對象,把來源物件的下標改為2,在這個例子裡面 是走這裡的因為第一個參數是ture然後把deep變成了true ,target被修正成了第二個參數也即是obj1,源物件的起始下標為2就是從第三個開始作為來源物件也就是本例中的obj2
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
這裡對target又進一步進行了處理對於非物件和函數的資料型別而言增加自訂屬性是無效的例如字串自能呼叫自帶的方法和屬性
// extend jQuery itself if only one argument is passed
if ( length === i ) {
target = this;
--i;
}
如果length屬性等於i的值那就表示沒有目標對象存在,正常情況下length應該是大於i的值的,那麼這個時候就把this作為目標對象把i值減一實現length值大於i值(比i大1)
這個就是jQuery給自己擴充屬性的方法的實作原理,只要不傳入目標物件就可以啦
兩個可能的情況:$.extend(obj) 或 $.extend(false/true,obj);
for ( ; i
// 只處理非空/未定義值
if ( (選項 = 參數[ i ]) != null ) {
// 擴充基礎物件
for ( 選項中的名稱 ) {
src = 目標[ 名稱 ];
複製=選項[姓名];
// 防止永無止境的循環
if ( 目標 === 複製 ) {
續;
}
// 若我們想要合併一般物件或陣列,則遞迴
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
if (copyIsArray) {
copyIsArray = false;
複製= src && jQuery.isArray(src) ?原始碼:[];
;
} 其他 {
克隆 = src && jQuery.isPlainObject(src) ?原始碼:{};
}
// 絕不移動原始對象,而複製它們
target[ name ] = jQuery.extend( deep,clone, copy );
// 請勿引入未定義的數值
} else if (複製!==未定義) {
目標[姓名]=複製;
}
}
}
}
這部分就是這個方法的核心了,從arguements物件的第i個下標值開始循環運算先過濾掉來源物件是null或是undefined的情況可以看到其實
來源物件不一定真的就是物件,也可以是其他類型的值例如字串例如這樣寫:
console.log($.extend({'name':'tom'},'aa')); //物件 {0: "a", 1: "a", name: "tom"}
是不是感覺很奇怪啊?到底是怎麼實現的呢?下面繼續看
過濾完成之後開始進行for循環 src保存的是目標物件的某個鍵的值,copy屬性保存的來源物件的某個鍵的值,這兩個鍵都是一樣的
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
如果來源物件的某個屬性值就是目標物件可能會造成死循環導致程式崩潰所以這裡做了一個限制讓其跳過此次循環例如:
var o = {};
o.n1 = o;
$.extend( true, o, { n2: o } );
// 拋出例外:
// Uncaught RangeError: Maximum call stack size exceeded
但是這樣做也會冤枉一些正常的狀況例如:
var obj1={a:'a'}
var obj2={a:obj1};
console.log($.extend(obj1,obj2)); //Object {a: "a"}
這種情況也是滿足來源物件值等於目標物件的但是結果發現obj1的a的屬性值並沒有被修改,就是因為執行了continue,下面把源碼的這段話註解掉在執行
Object {a: Object}
這個時候就是正常被修改了個人感覺這個地方需要改進;
接著就是一個if判斷就是區分是不是進行深度複製的先不看深度複製的先看一般的
target[ name ] = copy;
很簡單就是只要copy有值就直接複製給目標對象,目標對像有的就修改沒有就增加,這樣就實現了合併啦。
for迴圈之後在把新的目標物件傳回,所以目標物件最後是被修改的,而且結果和回傳的結果是一樣的。
// Return the modified object
return target;
};
下面再來說深度複製了怎麼去處理
先保證deep是true,copy有值且是物件或陣列(如果不是物件和陣列深度複製也就無從談起)然後再分數群組和物件來處理,先來看陣列的情況:
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
如果是數組copyIsArray的值為真然後走裡面的把值改成false ,針對當前循環的源對象屬性,目標對象可能有也可能沒有,有的話判斷一下是不是數組是的話就是原來的數組不變不是的話就讓它變成數組,因為既然來源物件的當前屬性是數組最後目標元素也必須是數組。不是數組就是物件把目標物件目前屬性改成物件。
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
然後把來源物件的目前屬性值(是陣列或物件)和已經被改造過的目標物件的目前屬性進行遞歸合併最後傳回的新的陣列或物件賦值給目標對象,最終實作了深度複製。
但這裡面還有一個比較奇怪的現象,例如這樣操作:
console.log($.extend({a:1},'aa')); //Object {0: "a", 1: "a", a: 1}
原來源物件不一定真的是物件e而且居然可以把字串拆開跟目標物件合併,原來for...in迴圈是操作字串的
var str='aa';
for(var name in str){
console.log(name);
console.log(str[name])
}
這樣也是可以的,會把字串拆開按數字下標讀取,但是在源碼中
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) )
是有陣列和物件限制的,那麼深度複製的時候是不是就沒有效果了呢?
經過我測試深度複製也是可以的,因為在源碼裡面copy的值竟然變成了匿名函數函數
alert(jQuery.isPlainObject(copy)); //true
至於為什麼是函數筆者還沒搞清楚留待以後解決吧!