這原本是StackOverFlow裡的一個提問,看到答案後受益良多,於是翻譯一下下跟大家分享,原文地址:http://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript
淺複製我就不說了,全部是引用對象,網上列出的深複製的幾個例子也都有或多或少的問題,我都試過,某些特殊情況下會出現奇怪的問題,這裡擺出一個比較完美的深複製函數(別直接複製了拿去用哦,裡面有互動的內容):
function clone(item) { if (!item) { return item; } // null, undefined values check var types = [ Number, String, Boolean ], result; // normalizing primitives if someone did new String('aaa'), or new Number('444'); //一些通過new方式建立的東東可能會類型發生變化,我們在這裡要做一下正常化處理 //比如new String('aaa'), or new Number('444') types.forEach(function(type) { if (item instanceof type) { result = type( item ); } }); if (typeof result == "undefined") { if (Object.prototype.toString.call( item ) === "[object Array]") { result = []; item.forEach(function(child, index, array) { result[index] = clone( child ); }); } else if (typeof item == "object") { // testign that this is DOM //如果是dom對象,那麼用內建的cloneNode處理 if (item.nodeType && typeof item.cloneNode == "function") { var result = item.cloneNode( true ); } else if (!item.prototype) { // check that this is a literal // it is an object literal //如果是個對象迭代的話,我們可以用for in 迭代來賦值 result = {}; for (var i in item) { result[i] = clone( item[i] ); } } else { // depending what you would like here, // just keep the reference, or create new object //這裡解決的是帶建構函式的情況,這裡要看你想怎麼複製了,深得話,去掉那個false && ,淺的話,維持原有的引用, //但是我不建議你去new一個建構函式來進行深複製,具體原因下面會解釋 if (false && item.constructor) { // would not advice to do that, reason? Read below //朕不建議你去new它的建構函式 result = new item.constructor(); } else { result = item; } } } else { result = item; } } return result;}var copy = clone({ one : { 'one-one' : new String("hello"), 'one-two' : [ "one", "two", true, "four" ] }, two : document.createElement("div"), three : [ { name : "three-one", number : new Number("100"), obj : new function() { this.name = "Object test"; } } ]})
上面的這個函數複製一些{}裡的玩意兒一點問題都沒有,考慮的情況很全面,很周全。但是,現在還沒算完,我們來討論下,如何複製一個“真正”的對象,比如你通過建構函式建立了個對象:
var User = function(){};var newuser = new User();
這種情況的話用上面的函數是完全沒問題的,所有的對象的屬性全部暴露在外,換句話說都是公有的,public的,隨便訪問的,沒有問題。你可以用for in去迭代裡面的屬性,不管什麼類型,去掉那個“false &&”就可以用,完全沒問題。但是,這裡存在著一個風險--要知道,對象或執行個體的屬性不可能全部是公有的,一旦存在私人變數(原文中叫狀態state),你這樣的複製便毫無意義,因為會丟失這些資料。比如下面的代碼:
function Man() { var age = 1; this.getAge = function () { return age; } this.grow = function () { age += 1; }}var man = new Man();
裡面的age這個東東,就會出現複製不了的情況了。
除此之外,還有另外一個問題,想象一個沒有這些私人變數的情況下,這些代碼木有問題,但是,一些對象的建構函式是有參數的,比如我要這樣建立一個對象:
new User({ bike : someBikeInstance});
這種情況下你估計要悲劇,someBikeInstance這貨很有可能是在另外的一些上下文中建立的,這個我們擷取不來的,至少對於這個函數來說是這樣。。
那該咋辦呢?其實沒什麼好的解決辦法,要麼用for in去迭代,丟掉那些私人變數,要麼還是用引用,這些都會出現或多或少的問題,另外一個解決方案是在所有的要被複製的對象裡單獨建立一個複製函數的屬性,就像DOM裡的複製節點函數cloneNode一樣。
初次翻譯,有錯誤的話,還望諒解!^_^