$.extend(true, obj1, obj2);
alert(obj1.x.xxx); // 得到"xxx"
obj2.x.xxx = 'zzz';
alert(obj2.x.xxx); // 得到"zzz"
alert(obj1.x.xxx); // 得帶"xxx"
</script>
$.extend(true, obj1, obj2)表示以obj2中的屬性擴展對象obj1,第一個參數設為true表示深復制。
雖然obj1中原來沒有"x"屬性,但經過擴展後,obj1不但具有了"x"屬性,而且對obj2中的"x"屬性的修改也不會影響到obj1中"x"屬性的值,這就是所謂的“深復制”了。
淺復制的實現
如果僅僅需要實現淺復制,可以采用類似下面的寫法:
復制代碼 代碼如下:
也就是簡單地將options中的屬性復制到target中。我們仍然可以用類似的代碼進行測試,但得到的結果有所不同(假設我們的js命名為“jquery-extend.js”):
obj1中具有了"x"屬性,但這個屬性是一個對象,對obj2中的"x"的修改也會影響到obj1,這可能會帶來難以發現的錯誤。
深復制的實現
如果我們希望實現“深復制”,當所復制的對象是數組或者對象時,就應該遞歸調用extend。如下代碼是“深復制”的簡單實現:
復制代碼 代碼如下:
具體分為三種情況:
1. 屬性是數組時,則將target[name]初始化為空數組,然後遞歸調用extend;
2. 屬性是對象時,則將target[name]初始化為空對象,然後遞歸調用extend;
3. 否則,直接復制屬性。
測試代碼如下:
復制代碼 代碼如下:
現在如果指定為深復制的話,對obj2的修改將不會對obj1產生影響了;不過這個代碼還存在一些問題,比如“instanceof Array”在IE5中可能存在不兼容的情況。jQuery中的實現實際上會更復雜一些。
更完整的實現
下面的實現與jQuery中的extend()會更接近一些:
復制代碼 代碼如下:class2type = {
'[object Boolean]' : 'boolean',
'[object Number]' : 'number',
'[object String]' : 'string',
'[object Function]' : 'function',
'[object Array]' : 'array',
'[object Date]' : 'date',
'[object RegExp]' : 'regExp',
'[object Object]' : 'object'
},
type = function(obj) {
return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";
},
isWindow = function(obj) {
return obj && typeof obj === "object" && "setInterval" in obj;
},
isArray = Array.isArray || function(obj) {
return type(obj) === "array";
},
isPlainObject = function(obj) {
if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {
return false;
}
if (obj.constructor && !hasOwn.call(obj, "constructor")
&& !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
var key;
for (key in obj) {
}
return key === undefined || hasOwn.call(obj, key);
},
extend = function(deep, target, options) {
for (name in options) {
src = target[name];
copy = options[name];
if (target === copy) { continue; }
if (deep && copy
&& (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && isArray(src) ? src : [];
} else {
clone = src && isPlainObject(src) ? src : {};
}
target[name] = extend(deep, clone, copy);
} else if (copy !== undefined) {
target[name] = copy;
}
}
return target;
};
return { extend : extend };
}();
首先是 $ = function(){...}();這種寫法,可以理解為與下面的寫法類似:
也就是立即執行函數,並將結果賦給$。這種寫法可以利用function來管理作用域,避免局部變量或局部函數影響全局域。另外,我們只希望使用者調用$.extend(),而將內部實現的函數隱藏,因此最終返回的對象中只包含extend:
接下來,我們看看extend函數與之前的區別,首先是多了這句話:
這是為了避免無限循環,要復制的屬性copy與target相同的話,也就是將“自己”復制為“自己的屬性”,可能導致不可預料的循環。
然後是判斷對象是否為數組的方式:
復制代碼 代碼如下:
如果浏覽器有內置的Array.isArray 實現,就使用浏覽器自身的實現方式,否則將對象轉為String,看是否為"[object Array]"。
最後逐句地看看isPlainObject的實現:
復制代碼 代碼如下:
如果定義了obj.nodeType,表示這是一個DOM元素;這句代碼表示以下四種情況不進行深復制:
1. 對象為undefined;
2. 轉為String時不是"[object Object]";
3. obj是一個DOM元素;
4. obj是window。
之所以不對DOM元素和window進行深復制,可能是因為它們包含的屬性太多了;尤其是window對象,所有在全局域聲明的變量都會是其屬性,更不用說內置的屬性了。
接下來是與構造函數相關的測試:
復制代碼 代碼如下:
如果對象具有構造函數,但卻不是自身的屬性,說明這個構造函數是通過prototye繼承來的,這種情況也不進行深復制。這一點可以結合下面的代碼結合進行理解:
這幾句代碼是用於檢查對象的屬性是否都是自身的,因為遍歷對象屬性時,會先從自身的屬性開始遍歷,所以只需要檢查最後的屬性是否是自身的就可以了。
這 說明如果對象是通過prototype方式繼承了構造函數或者屬性,則不對該對象進行深復制;這可能也是考慮到這類對象可能比較復雜,為了避免引入不確定 的因素或者為復制大量屬性而花費大量時間而進行的處理,從函數名也可以看出來,進行深復制的只有"PlainObject"。
如果我們用如下代碼進行測試:
function X() {
this.xxx = 'xxx';
}
X.prototype = new O();
x = new X();
obj1 = { a : 'a', b : 'b' };
obj2 = { x : x };
$.extend(true, obj1, obj2);
alert(obj1.x.yyy); // 得到"xxx"
obj2.x.yyy = 'zzz';
alert(obj1.x.yyy); // 得到"zzz"
</script>
可以看到,這種情況是不進行深復制的。
總之,jQuery中的extend()的實現方式,考慮了兼容浏覽器的兼容,避免性能過低,和避免引入不可預料的錯誤等因素。