關於JS中的淺拷貝(shallow copy)以及深拷貝(deep copy)

Andy Chen
Andy的技術分享blog
5 min readJun 1, 2019

前言

筆者最近在準備面試考題的時候,無意間看到了是否了解 JS 中的淺拷貝以及深拷貝,後來仔細想了一下發現還真的不太清楚完整的運作原理,所以決定寫一篇文章來釐清一下自己的觀念XD

淺拷貝V.S深拷貝

淺拷貝VS深拷貝

在開始正式進入文章主題之前,筆者先來簡單闡述一下什麼是淺拷貝什麼是深拷貝,看上面那張圖就知道,淺拷貝就是共用同一個記憶體空間,而深拷貝則是兩個不同的記憶體空間,以觀念來說非常的簡單,但這也是 JS 最多地雷的地方XD

為什麼會這麼說呢?接下來就用幾個簡單的小題目帶大家了解 JS 對於淺拷貝以及深拷貝的操作吧!

淺拷貝(shallow copy)

先來小試身手一下,底下有一段程式碼,大家在貼到 dev tool 上面執行之前,不妨先想想看會輸出什麼值。

let obj1 = {a: 1}
let obj2 = obj1
obj1.a = 2
console.log(obj2.a) // ?

先不要以JS的邏輯去看,依照正常的程式運作原理應該會輸出 1 才對,因為運作的原理是 call by value,但 JS 不一樣,JS 對於物件的操作是 call by reference,所以上面程式碼中的 obj1 以及 obj2 其實是共用同一個記憶體,既然是共用同一個記憶體想當然只要有一方的值改變了另一方也會跟著改變,這個就叫做淺拷貝。

在回到剛剛的例子,聰明的大家應該會想到另一個做法,我就直接創出一個新的物件,這樣兩個物件就不會影響到彼此啦!

let obj1 = {a: 1}
let obj2 = {a: obj1.a}
obj1.a = 2
console.log(obj2.a) // 1

乍看之下好像真的是兩個不同的物件,的確在這種情況下是一種深拷貝,但在某些情況下他其實還是屬於一種淺拷貝,筆者就曾經因為這樣被雷很多次XD

如果我今天稍微改變一下obj1的架構,變成這樣:

let obj1 = {a: {a: 1}}
let obj2 = {a: obj1.a}
obj1.a.a = 2
console.log(obj2.a) // 輸出{a: 2}而不是{a: 1}

這時候 obj2 內的值也變了,所以我們可以發現,只要物件超過一層,這種作法只會複製表層而已,深層的內容還是共用同一個記憶體,因此兩者還是會互相影響。

shallow copy

常見的小陷阱

  • … operator

相信大家都知道 ES6 有一個新的 operator,利用 ...array 或 …obj 的方式達到展開(spread)的效果,我們可以把剛剛的例子用這個 operator 來達到更簡單的寫法。

let obj1 = {a: 1}
let obj2 = {...obj1}
obj1.a = 2
console.log(obj2.a) // 1

完美,看起來又是一種深拷貝了,但其實這個也不算是深拷貝,這個跟剛剛的例子一樣只要稍微變化一下他又變成淺拷貝了。

let obj1 = {a: {a: 1}}
let obj2 = {...obj1}
obj1.a.a = 2
console.log(obj2.a) // {a: 2}

簡單來說就是將來源的 object 分派給指定的物件。

let obj1 = {a: 1}
let obj2 = Object.assign({}, obj1)
obj1.a = 2
console.log(obj2.a) // 1

這個也是在 ES6 推出的物件操作方法,雖然上面的 ...operator 是淺拷貝,但筆者決定再給 ES6 一個機會,所以沒意外應該就是深拷貝了吧!但其實這個稍微變化一下又是淺拷貝了。

let obj1 = {a: {a: 1}}
let obj2 = Object.assign({}, obj1)
obj1.a.a = 2
console.log(obj2.a) // {a: 2}

所以你說 JS 是不是在搞各位開發者,ES6 自己推出的兩個 method 都隱藏了非常多的地雷等著大家去踩,俗話說的好:前人踩雷,後人乘涼,希望大家看完這篇文章之後不會再踩到一樣的地雷了XD

深拷貝(deep copy)

講了這麼多淺拷貝,接下來終於要來講深拷貝的操作了,看了上面這麼多例子應該就發現不能用一些簡單的方法來進行深拷貝了,筆者這邊要來介紹幾個深拷貝的操作。

如果想要真的進行深拷貝的話,就回歸最基本的 JSON 操作吧XD

將物件轉成字串再轉成物件,這樣就真的可以確保出來的會是一個新的物件而且是使用不同的記憶體。

let obj1 = {a: {a: 1}}
let obj2 = JSON.parse(JSON.stringify(obj1))
obj1.a.a = 2
console.log(obj2.a) // {a: 1}

lodash 是 JS 中一套非常強大的 utility library,裡面有一個 API 叫 _.cloneDeep(obj) ,這個就會回傳一個利用深拷貝而得到的新物件,這個方法也是比較簡單而且看起來也比較乾淨的作法。

let obj1 = {a: {a: 1}}
let obj2 = _.cloneDeep(obj1)
obj1.a.a = 2
console.log(obj2.a) // {a: 1}

總結

講了這麼多不曉得大家對於淺拷貝以及深拷貝有沒有更深入的了解了XD

如果看完文章有任何問題或是建議都歡迎在下方的留言區留言給我,筆者每個留言都會看也都會留言喔!

--

--

Andy Chen
Andy的技術分享blog

嗨嗨我是Andy,用嘴巴工作的工程師😂,喜歡學習不同領域的內容,專長為網頁開發,歡迎大家跟我聊技術~