extend()函數是jQuery的基礎函數之一,做用是擴展示有的對象javascript
<script type="text/javascript" src="jquery-1.5.2.js"></script> <script> obj1 = { a : 'a', b : 'b' }; obj2 = { x : { xxx : 'xxx', yyy : 'yyy' }, y : 'y' }; $.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"屬性的值,這就是所謂的「深複製」了。
淺複製的實現java
若是僅僅須要實現淺複製,能夠採用相似下面的寫法: $ = { extend : function(target, options) { for (name in options) { target[name] = options[name]; } return target; } }; 也就是簡單地將options中的屬性複製到target中。咱們仍然能夠用相似的代碼進行測試,但獲得的結果有所不一樣(假設咱們的js命名爲「jquery-extend.js」): <script type="text/javascript" src="jquery-extend.js"></script> <script> obj1 = { a : 'a', b : 'b' }; obj2 = { x : { xxx : 'xxx', yyy : 'yyy' }, y : 'y' }; $.extend(obj1, obj2); alert(obj1.x.xxx); // 獲得"xxx" obj2.x.xxx = 'zzz'; alert(obj2.x.xxx); // 獲得"zzz" alert(obj1.x.xxx); // 得帶"zzz" </script> obj1中具備了"x"屬性,但這個屬性是一個對象,對obj2中的"x"的修改也會影響到obj1,這可能會帶來難以發現的錯誤。
深複製的實現 若是咱們但願實現「深複製」,當所複製的對象是數組或者對象時,就應該遞歸調用extend。以下代碼是「深複製」的簡單實現: $ = { extend : function(deep, target, options) { for (name in options) { copy = options[name]; if (deep && copy instanceof Array) { target[name] = $.extend(deep, [], copy); } else if (deep && copy instanceof Object) { target[name] = $.extend(deep, {}, copy); } else { target[name] = options[name]; } } return target; } };
具體分爲三種狀況:
1. 屬性是數組時,則將target[name]初始化爲空數組,而後遞歸調用extend;
2. 屬性是對象時,則將target[name]初始化爲空對象,而後遞歸調用extend;
3. 不然,直接複製屬性。node
測試代碼以下: <script type="text/javascript" src="jquery-extend.js"></script> <script> obj1 = { a : 'a', b : 'b' }; obj2 = { x : { xxx : 'xxx', yyy : 'yyy' }, y : 'y' }; $.extend(true, obj1, obj2); alert(obj1.x.xxx); // 獲得"xxx" obj2.x.xxx = 'zzz'; alert(obj2.x.xxx); // 獲得"zzz" alert(obj1.x.xxx); // 獲得"xxx" </script>
如今若是指定爲深複製的話,對obj2的修改將不會對obj1產生影響了;不過這個代碼還存在一些問題,好比「instanceof Array」在IE5中可能存在不兼容的狀況。jQuery中的實現實際上會更復雜一些。
更完整的實現jquery
下面的實現與jQuery中的extend()會更接近一些: $ = function() { var copyIsArray, toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty; 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(){...}();這種寫法,能夠理解爲與下面的寫法相似:數組
func = function(){...};
$ = func();瀏覽器
也就是當即執行函數,並將結果賦給$。這種寫法能夠利用function來管理做用域,避免局部變量或局部函數影響全局域。另外,咱們只但願使用者調用$.extend(),而將內部實現的函數隱藏,所以最終返回的對象中只包含extend:函數
return { extend : extend };性能
接下來,咱們看看extend函數與以前的區別,首先是多了這句話:測試
if (target === copy) { continue; }this
這是爲了不無限循環,要複製的屬性copy與target相同的話,也就是將「本身」複製爲「本身的屬性」,可能致使不可預料的循環。
而後是判斷對象是否爲數組的方式:
type = function(obj) {
return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";
},
isArray = Array.isArray || function(obj) {
return type(obj) === "array";
}
若是瀏覽器有內置的Array.isArray 實現,就使用瀏覽器自身的實現方式,不然將對象轉爲String,看是否爲"[object Array]"。
最後逐句地看看isPlainObject的實現:
if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {
return false;
}
若是定義了obj.nodeType,表示這是一個DOM元素;這句代碼表示如下四種狀況不進行深複製:
1. 對象爲undefined;
2. 轉爲String時不是"[object Object]";
3. obj是一個DOM元素;
4. obj是window。
之因此不對DOM元素和window進行深複製,多是由於它們包含的屬性太多了;尤爲是window對象,全部在全局域聲明的變量都會是其屬性,更不用說內置的屬性了。
接下來是與構造函數相關的測試:
if (obj.constructor && !hasOwn.call(obj, "constructor")
&& !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
若是對象具備構造函數,但卻不是自身的屬性,說明這個構造函數是經過prototye繼承來的,這種狀況也不進行深複製。這一點能夠結合下面的代碼結合進行理解:
var key;
for (key in obj) {
}
return key === undefined || hasOwn.call(obj, key);
這幾句代碼是用於檢查對象的屬性是否都是自身的,由於遍歷對象屬性時,會先從自身的屬性開始遍歷,因此只須要檢查最後的屬性是不是自身的就能夠了。
這說明若是對象是經過prototype方式繼承了構造函數或者屬性,則不對該對象進行深複製;這可能也是考慮到這類對象可能比較複雜,爲了不引入不肯定的因素或者爲複製大量屬性而花費大量時間而進行的處理,從函數名也能夠看出來,進行深複製的只有"PlainObject"。
若是咱們用以下代碼進行測試:
<script type="text/javascript" src="jquery-1.5.2.js"></script> <script> function O() { this.yyy = 'yyy'; } 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()的實現方式,考慮了兼容瀏覽器的兼容,避免性能太低,和避免引入不可預料的錯誤等因素。
詳細出處參考:http://www.jb51.net/article/39288.htm