如何正確複製 JavaScript 物件?
P粉334721359
P粉334721359 2023-08-23 12:26:59
0
2
436
<p>我有一個物件<code>x</code>。我想將其複製為物件 <code>y</code>,這樣對 <code>y</code> 的變更就不會修改 <code>x</code>。我意識到複製從內建 JavaScript 物件派生的物件會產生額外的、不需要的屬性。這不是問題,因為我正在複製我自己的文字構造的物件之一。 </p> <p>如何正確複製 JavaScript 物件? </p>
P粉334721359
P粉334721359

全部回覆(2)
P粉691958181

如果您在物件中不使用日期、函數、未定義、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粉122932466

2022 年更新

有一個新的 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#、ArrayDate代码>、字串代码>、數字代码>或布林代码>。最後 3 種類型是不可變的,因此我可以執行淺複製而不用擔心它會改變。我進一步假設 ObjectArray 中包含的任何元素也將是該清單中的 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 對象,但它可能足以滿足許多目的,只要您不認為它只適用於您扔給它的任何東西。

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!