首頁 > web前端 > js教程 > 淺與深層複製的JavaScript

淺與深層複製的JavaScript

Jennifer Aniston
發布: 2025-02-09 08:28:10
原創
903 人瀏覽過

Shallow vs. Deep Copying in JavaScript

JavaScript 對象的複制並非表面看起來那麼簡單。理解此過程中對象和引用的工作方式對於 Web 開發人員至關重要,可以節省數小時的調試時間。當您使用大型有狀態應用程序(例如在 React 或 Vue 中構建的應用程序)時,這一點變得越來越重要。

淺複製和深複製指的是我們在 JavaScript 中如何創建對象的副本以及在“副本”中創建了哪些數據。在本文中,我們將深入探討這些方法之間的區別,探討它們的實際應用,並揭示使用它們時可能出現的潛在陷阱。

關鍵要點

  • JavaScript 中的淺複製創建一個新對象,該對象複製現有對象的屬性,但保留對原始值或對象的相同引用。這意味著對淺副本中嵌套對象的修改也會影響原始對象和任何其他淺副本。
  • 另一方面,深複製會創建現有對象的精確副本,包括其所有屬性和任何嵌套對象,而不是僅僅是引用。當您需要兩個不共享引用的獨立對象時,這使得深複製變得有益,從而確保對一個對象的更改不會影響另一個對象。
  • 雖然深複製提供了數據準確性的好處,但它也可能存在一些缺點,例如性能影響、內存消耗增加、循環引用問題、函數和特殊對象處理以及實現複雜性。因此,務必評估對於每個特定用例是否需要深複製。

什麼是“淺”複製

淺複製是指創建新對象的過程,該對像是現有對象的副本,其屬性引用與原始對象相同的數值或對象。在 JavaScript 中,這通常是使用 Object.assign() 或展開語法({...originalObject})等方法實現的。淺複製僅創建對現有對像或值的新的引用,不會創建深複製,這意味著嵌套對象仍然是引用的,而不是重複的。

讓我們看看下面的代碼示例。新創建的對象 shallowCopyZoo 是通過展開運算符創建的 zoo 的副本,這導致了一些意外的後果。

let zoo = {
  name: "Amazing Zoo",
  location: "Melbourne, Australia",
  animals: [
    {
      species: "Lion",
      favoriteTreat: "?",
    },
    {
      species: "Panda",
      favoriteTreat: "?",
    },
  ],
};

let shallowCopyZoo = { ...zoo };
shallowCopyZoo.animals[0].favoriteTreat = "?";
console.log(zoo.animals[0].favoriteTreat); 
// "?",而不是 "?"
登入後複製
登入後複製

但是讓我們看看 shallowCopyZoo 中究竟是什麼。屬性 namelocation 是原始值(字符串),因此它們的數值被複製。但是,animals 屬性是對象的數組,因此復制的是對該數組的引用,而不是數組本身。

您可以使用嚴格相等運算符 (===) 快速測試這一點(如果您不相信我)。只有當對象引用同一對象時,一個對象才等於另一個對象(參見原始數據類型與引用數據類型)。請注意,animals 屬性在兩者上是相等的,但對象本身並不相等。

let zoo = {
  name: "Amazing Zoo",
  location: "Melbourne, Australia",
  animals: [
    {
      species: "Lion",
      favoriteTreat: "?",
    },
    {
      species: "Panda",
      favoriteTreat: "?",
    },
  ],
};

let shallowCopyZoo = { ...zoo };
shallowCopyZoo.animals[0].favoriteTreat = "?";
console.log(zoo.animals[0].favoriteTreat); 
// "?",而不是 "?"
登入後複製
登入後複製

這可能導致代碼庫中出現潛在問題,並且在處理大型修改時尤其困難。修改淺副本中的嵌套對像也會影響原始對象和任何其他淺副本,因為它們都共享相同的引用。

深複製

深複製是一種創建新對象的技巧,該對像是現有對象的精確副本。這包括複製其所有屬性和任何嵌套對象,而不是引用。當您需要兩個不共享引用的獨立對象時,深克隆很有用,確保對一個對象的更改不會影響另一個對象。

程序員在處理複雜應用程序中的應用程序狀態對象時經常使用深克隆。創建新的狀態對象而不影響先前狀態對於維護應用程序的穩定性和正確實現撤消/重做功能至關重要。

如何使用 JSON.stringify()JSON.parse() 進行深複製

一種流行且無需庫的深複製方法是使用內置的 JSON.stringify()JSON.parse() 方法。

parse(stringify()) 方法並不完美。例如,Date 等特殊數據類型將被轉換為字符串,未定義的值將被忽略。與本文中的所有選項一樣,應根據您的具體用例進行考慮。

在下面的代碼中,我們將使用這些方法創建一個 deepCopy 函數來深度克隆一個對象。然後,我們複製 playerProfile 對象並修改複製的對象,而不會影響原始對象。這展示了深複製在維護不共享引用的獨立對象方面的價值。

console.log(zoo.animals === shallowCopyZoo.animals)
// true

console.log(zoo === shallowCopyZoo)
// false
登入後複製

用於深複製的庫

還有一些各種第三方庫提供了深複製解決方案。

  • Lodash 庫的 cloneDeep() 函數可以正確處理循環引用、函數和特殊對象。
  • jQuery 庫的 extend() [deep = true] 函數
  • immer 庫是為 React-Redux 開發人員構建的,並提供用於修改對象的便捷工具。

Vanilla JS 深複製函數

如果由於某種原因您不想使用 JSON 對像或第三方庫,您也可以在 Vanilla JavaScript 中創建一個自定義深複製函數。該函數遞歸迭代對象屬性並創建一個具有相同屬性和值的新對象。

const playerProfile = {
  name: 'Alice',
  level: 10,
  achievements: [
    {
      title: 'Fast Learner',
      emoji: '?'
    },
    {
      title: 'Treasure Hunter',
      emoji: '?'
    }
  ]
};

function deepCopy(obj) {
  return JSON.parse(JSON.stringify(obj));
}

const clonedProfile = deepCopy(playerProfile);

console.log(clonedProfile);
// 输出与playerProfile相同

// 修改克隆的配置文件而不影响原始配置文件
clonedProfile.achievements.push({ title: 'Marathon Runner', emoji: '?' });
console.log(playerProfile.achievements.length); // 输出:2
console.log(clonedProfile.achievements.length); // 输出:3
登入後複製

深複製的缺點

雖然深複製為數據準確性提供了極大的好處,但建議評估對於每個特定用例是否需要深複製。在某些情況下,淺複製或其他管理對象引用的技術可能更合適,從而提供更好的性能和降低複雜性。

  1. 性能影響:深複製在計算上可能代價高昂,尤其是在處理大型或複雜對象時。由於深複製過程會迭代所有嵌套屬性,因此可能需要大量時間,從而對應用程序的性能產生負面影響。
  2. 內存消耗:創建深複製會導致複製整個對象層次結構,包括所有嵌套對象。這可能導致內存使用增加,這在內存受限的環境中或處理大型數據集時可能成為問題。
  3. 循環引用:當對象包含循環引用(即當一個對象的屬性直接或間接地引用自身時)時,深複製可能會導致問題。循環引用可能導致深複製過程中的無限循環或堆棧溢出錯誤,處理它們需要額外的邏輯來避免這些問題。
  4. 函數和特殊對象處理:深複製可能無法按預期處理函數或具有特殊特徵的對象(例如,DateRegExp、DOM 元素)。例如,當深複製包含函數的對象時,可能會復制函數的引用,但不會復制函數的閉包及其綁定的上下文。同樣,具有特殊特徵的對像在深複製時可能會丟失其獨特的屬性和行為。
  5. 實現複雜性:編寫自定義深複製函數可能很複雜,而像 JSON.parse(JSON.stringify(obj)) 這樣的內置方法也有一些限制,例如無法正確處理函數、循環引用或特殊對象。雖然有一些第三方庫(如 Lodash 的 _.cloneDeep())可以更有效地處理深複製,但為深複製添加外部依賴項可能並不總是理想的。

結論

感謝您抽出時間閱讀本文。淺複製與深複製比任何初學者想像的都要復雜得多。儘管每種方法都有很多陷阱,但花時間審查和考慮這些選項將確保您的應用程序和數據保持您想要的樣子。

關於 JavaScript 中淺複製與深複製的常見問題解答 (FAQ)

JavaScript 中淺複製和深複製的主要區別是什麼?

淺複製和深複製之間的主要區別在於它們處理作為對象的屬性的方式。在淺複製中,複製的對象與原始對象共享對嵌套對象的相同引用。這意味著對嵌套對象的更改將反映在原始對象和復制對像中。另一方面,深複製會創建嵌套對象的新實例,這意味著對複制對像中嵌套對象的更改不會影響原始對象。

展開運算符如何在淺複製中工作?

JavaScript 中的展開運算符(…)通常用於淺複製。它將一個對象的所有可枚舉自身屬性複製到另一個對象。但是,它只複製第一級的屬性和嵌套對象的引用。因此,對嵌套對象的更改將影響原始對象和復制的對象。

我可以使用 JSON 方法進行深複製嗎?

是的,您可以使用 JSON 方法在 JavaScript 中進行深複製。 JSON.stringify()JSON.parse() 方法的組合可以創建對象的深複製。 JSON.stringify() 將對象轉換為字符串,JSON.parse() 將字符串解析回新對象。但是,此方法有一些限制,因為它不會復制方法,並且不適用於 DateRegExpMapSet 等特殊 JavaScript 對象。

淺複製的局限性是什麼?

淺複製只複製第一級的屬性和嵌套對象的引用。因此,如果原始對象包含嵌套對象,則對這些嵌套對象的更改將影響原始對象和復制的對象。這可能導致代碼中出現意外的結果和錯誤。

Object.assign() 方法如何在淺複製中工作?

Object.assign() 方法用於將一個或多個源對象的所有可枚舉自身屬性的值複製到目標對象。它返回目標對象。但是,它執行淺複製,這意味著它只複製第一級的屬性和嵌套對象的引用。

在 JavaScript 中深複製對象的最佳方法是什麼?

在 JavaScript 中深複製對象的最佳方法取決於代碼的具體要求。如果您的對像不包含方法或特殊的 JavaScript 對象,您可以使用 JSON.stringify()JSON.parse() 方法的組合。對於更複雜的對象,您可能需要使用 Lodash 等庫,該庫提供深克隆函數。

我可以使用展開運算符進行深複製嗎?

不可以,JavaScript 中的展開運算符只執行淺複製。它複製第一級的屬性和嵌套對象的引用。要執行深複製,您需要使用其他方法或庫。

深複製的性能影響是什麼?

深複製可能比淺複製更消耗資源,尤其對於大型對象而言。這是因為深複製為所有嵌套對象創建新實例,這可能會佔用更多內存和處理能力。

深複製如何處理循環引用?

JSON.stringify()JSON.parse() 等深複製方法不處理循環引用,並將拋出錯誤。循環引用發生在一個對象的屬性引用該對象本身時。要處理循環引用,您需要使用支持它的庫,例如 Lodash。

為什麼我應該關心淺複製和深複製之間的區別?

了解淺複製和深複製之間的區別對於管理 JavaScript 中的數據至關重要。它會影響您的對像如何相互交互。如果您不小心,淺複製會導致意外的結果和錯誤,因為對嵌套對象的更改會影響原始對象和復制的對象。另一方面,深複製確保您的複制對象與原始對象完全獨立。

以上是淺與深層複製的JavaScript的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板