P粉6919581812023-08-24 09:44:45
如果您在物件中不使用日期、函數、未定義、regExp 或 Infinity,則一個非常簡單的行是 JSON.parse(JSON.stringify(object))< /代码>:
const a = { string: 'string', number: 123, bool: false, nul: null, date: new Date(), // stringified undef: undefined, // lost inf: Infinity, // forced to 'null' } console.log(a); console.log(typeof a.date); // Date object const clone = JSON.parse(JSON.stringify(a)); console.log(clone); console.log(typeof clone.date); // result of .toISOString()
這適用於包含物件、陣列、字串、布林值和數字的所有類型的物件。
另請參閱這篇關於結構化複製演算法的文章瀏覽器,在向工作人員發布訊息或從工作人員發布訊息時使用。它還包含深度克隆功能。
P粉1229324662023-08-24 00:44:32
有一個新的 JS 標準,稱為結構化複製。它適用於許多瀏覽器(請參閱我可以使用)。
const clone = structuredClone(object);
要對 JavaScript 中的任何物件執行此操作都不會簡單或直接。您將遇到錯誤地從物件原型中取得屬性的問題,這些屬性應該保留在原型中而不是複製到新實例中。例如,如果您要為 Object.prototype
新增一個 clone
方法(如某些答案所述),則需要明確跳過該屬性。但是,如果在 Object.prototype
或其他中間原型中添加了您不知道的其他附加方法怎麼辦?在這種情況下,您將複製不應該複製的屬性,因此您需要使用 hasOwnProperty
方法。
除了不可枚舉的屬性之外,當您嘗試複製具有隱藏屬性的物件時,您還會遇到更棘手的問題。例如,prototype
是函數的隱藏屬性。此外,物件的原型是透過屬性 __proto__
引用的,該屬性也是隱藏的,並且不會被迭代來源物件屬性的 for/in 循環複製。我認為 __proto__ 可能是 Firefox 的 JavaScript 解釋器特有的,在其他瀏覽器中可能有所不同,但你明白了。並非所有事情都是可列舉的。如果您知道隱藏屬性的名稱,則可以複製它,但我不知道有什麼方法可以自動發現它。
尋求優雅解決方案的另一個障礙是正確設定原型繼承的問題。如果來源物件的原型是Object
,則只需使用{}
建立新的通用物件即可,但如果來源物件的原型是Object
的某個後代code>,那麼您將丟失該原型中使用hasOwnProperty
過濾器跳過的其他成員,或者原型中的其他成員,但一開始就不可枚舉。一種解決方案可能是呼叫來源物件的建構函式屬性來取得初始複製對象,然後複製屬性,但您仍然不會獲得不可枚舉的屬性。例如,日期 code>
物件將其資料儲存為隱藏成員:
function clone(obj) { if (null == obj || "object" != typeof obj) return obj; var copy = obj.constructor(); for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; } return copy; } var d1 = new Date(); /* Executes function after 5 seconds. */ setTimeout(function(){ var d2 = clone(d1); alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString()); }, 5000);
d1
的日期字串會比 d2
晚 5 秒。使一個 Date
與另一個相同的方法是呼叫 setTime
方法,但這是特定於 Date
類別的。我不認為這個問題有一個萬無一失的通用解決方案,儘管我很樂意犯錯!
當我必須實作一般深度複製時,我最終妥協了,假設我只需要複製一個普通的Object
#、Array
、Date代码>、<代码>字串代码>、<代码>數字代码>或<代码>布林代码>。最後 3 種類型是不可變的,因此我可以執行淺複製而不用擔心它會改變。我進一步假設
Object
或 Array
中包含的任何元素也將是該清單中的 6 種簡單類型之一。這可以透過如下程式碼來完成:
function clone(obj) { var copy; // Handle the 3 simple types, and null or undefined if (null == obj || "object" != typeof obj) return obj; // Handle Date if (obj instanceof Date) { copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = clone(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]); } return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); }
只要物件和陣列中的資料形成樹結構,上述函數就足以適用於我提到的 6 種簡單類型。也就是說,對物件中相同資料的引用不超過一次。例如:
// This would be cloneable: var tree = { "left" : { "left" : null, "right" : null, "data" : 3 }, "right" : null, "data" : 8 }; // This would kind-of work, but you would get 2 copies of the // inner node instead of 2 references to the same copy var directedAcylicGraph = { "left" : { "left" : null, "right" : null, "data" : 3 }, "data" : 8 }; directedAcyclicGraph["right"] = directedAcyclicGraph["left"]; // Cloning this would cause a stack overflow due to infinite recursion: var cyclicGraph = { "left" : { "left" : null, "right" : null, "data" : 3 }, "data" : 8 }; cyclicGraph["right"] = cyclicGraph;
它無法處理任何 JavaScript 對象,但它可能足以滿足許多目的,只要您不認為它只適用於您扔給它的任何東西。