JavaScript中的淺複製與深複製

發表於2023-09-22

前言

JavaScript中的淺複製和深複製是非常重要的概念,它們在處理物件和陣列時具有不同的作用。在程式設計中,經常需要複製資料以便進行各種操作,但必須注意複製的方式,以確保得到預期的結果。

淺複製是建立一個新物件或陣列,並將原始物件或陣列的引用複製給它。這意味著新物件和原始物件將共享相同的記憶體地址,修改其中一個物件的屬性或元素也會影響另一個物件。相反,深複製是建立一個完全獨立的物件或陣列,新的複製將具有與原始物件或陣列相同的值,但是它們在記憶體中是彼此獨立的,相互之間的修改不會互相影響。

本文小編將為大家介紹JavaScript中實現淺複製和深複製的不同方法,並提供示例程式碼作為輔助。

基本複製

下面是一個基本的複製,新的複製物件會專門開闢一塊記憶體空間——二者的型別、值都是獨立可變的,換句話說,他們是透過將值傳遞給新物件完成複製的。

//原始值複製
let x = 400
let y = x
x = "This string"
console.log(y)  //400
console.log(x)  //This string

當y被建立時,它的值被賦予了x的值(因為這是在執行時,x被重新賦值之前)。這裡重要的一點是,讀者可以透過建立另一個變數並將其分配給要複製的變數來快速將原始資料型別的精確值複製到單獨的記憶體空間中。請注意它是如何例項化的——const 不允許再進行更改。

​ (記憶體分配和原始賦值的視覺進展)

//小編可以走的更深一些,在上面的程式碼中,再將x設定為原始資料型別;
//當然了,小編都知道它們是在不同的記憶體空間,只不過值是相同的
let x = 400
let y = x
x = "This string"
console.log(y)  //400
console.log(x)  //This string
x = 400
console.log(x)  // 400
console.log(y)  // 400

淺複製

以下是一個展示淺複製的示例。在此示例中,複製了一個包含文字的淺物件。由於淺複製只會複製原始物件的引用而非值本身,所以被複製的物件和原始物件將共享相同的記憶體空間,即它們的值也將相同。需要注意的是,在 JavaScript 中,“淺物件”是指一種非巢狀且非原始的 JavaScript 資料型別。

// 淺物件的淺複製
let ShallowObj= { 
  key1: 1, 
  key2: 2, 
} 
let newObj = ShallowObj// 一個簡單的重新分配為 newObj 建立共享記憶體
ShallowObj.key1 = 5
console.log(shallowObj) // {key1: 5, key2: 2} 
console.log(newObj) // {key1: 5, key2: 2}

當重新分配ShallowObj中key1的值時,會導致newObj中key1的值也隨之發生改變。儘管這兩個物件具有不同的變數名稱,但它們實際上共享相同的記憶體空間。因此,如果需要更改shallowObj.key1的值,可以直接修改newObj.key1來獲得相同的結果。這在某些情況下非常有用,例如當需要表示一組具有相同屬性和值的特定物件時。然而,在執行時,可能需要給這些淺複製物件賦予不同的變數名稱,以滿足應用程式的需求,並作為不同的props傳遞給其他元件。透過使用不同的變數名稱,可以根據不同的目標在應用程式中對它們進行獨立操作,以實現所需的功能。

對淺物件進行深複製

//使用 Object.assign()
let myRadio = { podcasts: 19, 
                albums: 378, 
                playlists: 44 
              }
let deepCopyMyRadio = Object.assign( {}, myRadio )
deepCopyMyRadio.playlists = 62 // 只改變 deepCopyMyRadio
console.log(deepCopyMyRadio) // => { podcasts: 19, 
                                     albums: 378, 
                                    playlists: 62 
                                   }
console.log(myRadio)         // => { podcasts: 19, 
                                     albums: 378, 
                                     playlists: 44 
                                   }

ES6引入了一種新特性稱為擴充套件運算子。擴充套件運算子用三個連續的點"..."表示,並可以在程式碼的多個地方使用。通常情況下,擴充套件運算子會為給定物件的每個頂級屬性建立副本,並將它們擴充套件到新物件中。在特定情況下,可以選擇使用淺複製或深複製來處理巢狀物件。在本例中,展示的是淺物件的深複製,因此可以使用Object.assign()方法或以下示例即可。

//用擴充套件運算子深複製
let myRadio = { podcasts: 9, 
                albums: 38, 
                playlists: 4 
               }
let copyMyRadio = { ...myRadio }
myRadio.albums = 88 // 還是隻改變myRadio
console.log(myRadio) //=> { podcasts: 9, albums: 88, playlists: 4 }
console.log(copyMyRadio) //=> {podcasts: 9,albums: 38, playlists: 4}

不過這種寫法的問題在於,隨著專案規模和複雜性的增加,擴充套件運算子是一個有侷限的解決方案。擴充套件運算子可以處理淺物件的深複製(非巢狀),即將一個物件的頂級屬性複製到另一個物件中。然而,當涉及巢狀物件或多層級結構時,擴充套件運算子會遇到限制。它只能複製物件的第一層屬性,而無法遞迴地複製巢狀的物件。

​ (分配方式:someOtherVar = someVar)

下面我們來看一下展開運算子在處理巢狀物件的複雜性時,並不如預期。對於巢狀物件來說,擴充套件運算子只提供了第一層屬性的深複製,而對於所有巢狀的資料來說,它們與原始資料共享記憶體空間,實際上進行的是淺複製。

Population.total 在 city 和shallowCity 中共享一個記憶體點。擴充套件運算子獲取頂層資料並將其新增到單獨的記憶體空間;因此,shallowCity 的 name 屬性實際上已更改。

對深物件進行深拷[JSON.parse(JSON.stringify())]

為了解決巢狀物件的複雜性問題,下面向大家介紹如何在深物件中進行深複製。在 JavaScript 中,當需要複製巢狀物件或陣列時,深複製變得非常重要。深複製是一種建立獨立全新物件的方法,它遞迴地複製每個巢狀物件和陣列,有效地避免了使用共享記憶體帶來的修改問題。其中,最常用的深複製方法是使用JSON.parse(JSON.stringify(object))。該方法首先將原始物件序列化為 JSON 字串,然後再解析字串並建立一個新物件,以確保所有屬性和巢狀物件都被複制到全新的物件中。當然,需要注意的是該方法存在一定的侷限性,例如無法複製函式、正規表示式等非資料型別,並且在某些情況下可能會帶來效能問題。因此,在實際應用中,我們必須根據具體情況選擇適合的深複製方法,以取得效率和正確性的平衡。

​ (對深物件進行深複製)

總結

JavaScript中的淺複製複製物件是建立一個新物件,但巢狀物件仍然共享記憶體。而深複製則建立一個獨立的全新物件,包括巢狀物件在內都被完全複製。淺複製常用方法有Object.assign()和擴充套件運算子,而深複製可以使用JSON.parse(JSON.stringify())或第三方庫。深複製適用於修改副本且不影響原始物件的情況,但可能消耗更多資源和時間。瞭解這兩種複製方式的差異和應用場景是編寫健壯程式碼的關鍵。

相關文章