iT邦幫忙

2022 iThome 鐵人賽

DAY 3
0
Modern Web

前端蛇行撞牆記系列 第 3

Day3 前端蛇行撞牆記 - 深拷貝、淺拷貝

  • 分享至 

  • xImage
  •  

前言

上一篇講到pass by value/ reference/ sharing,了解基本型別及物件型別的儲存空間方式不同之後就可以來講淺拷貝(shallow copy)深拷貝(deep copy)了。

這邊的版本會用pass by value, pass by reference的方式來說~

複習一下:

  • 物件型別的都是pass by reference,也就是「傳址」,就是說物件型別的stack存的不是值而是一個地址,真正的值存在heap裡面。

如果要拷貝一個物件型別可以分為淺拷貝、深拷貝的方式。

淺拷貝 shallow copy

淺拷貝的意思是說只有一層的物件的話會是不同的物件,不會參考同個reference,但如果物件裡面還有物件的話,則第二層的參考還是會是同個reference。

所以不會完全的拷貝,只有第一層會是pass by value,再多一層就會是pass by reference,就會改變到原本的物件。

淺拷貝還有可以使用Object.assign()的方式,不過這裡就只介紹展開運算子的方式,因為例子都差不多,只是要知道這些都無法完全拷貝一個新的物件。

展開運算子 spread operator

ES6新增的展開運算子可以做到第一層的拷貝。

const a = {
  a: 1,
};

const b = { ...a };
b.a = 2;

console.log(b === a); // false;
console.log(b.a === a.a); // false

console.log(b); // { a: 2 }
console.log(a); // { a: 1 }

可以看到用這種方式a !== b,所以這兩個並不是相同的物件,我們就成功了嗎?

那把a的value變成一個物件{ a: 1 }

const a = {
  a: { a: 1 },
};

const b = { ...a };
b.a.a = 2;

console.log(b === a); //false;
console.log(b.a === a.a); //true
console.log(b.a.a === a.a.a); // true

console.log(b); // { a: { a: 2 } }
console.log(a); // { a: { a: 2 } }

雖然a, b並不是相同的物件了,但因為a的value又是一個物件,而這個物件型別就還是pass by reference!所以第二層的b.a === a.a // true 跟第三層的 b.a.a === a.a.a // true

我們又沒有用展開運算子再把第二層的物件再次拷貝一下,所以就算物件用展開運算子拷貝就不會是同個物件,卻顧不到裡面第二層的物件。

如果要變成深拷貝的話也不是不可能,可以用JSON.stringify把物件變成字串,再用JSON.parse()把他轉回來,這樣就可以完全變成獨立的物件,並不會有任何參考reference的地方了。

深拷貝 deep copy

深拷貝就是就算裡面還有第二層的物件也不會是參考同的reference,兩個物件會完全不相同。

JSON.parse(JSON.stringify(物件))

第一個實作來用JSON來做:

const c = JSON.parse(JSON.stringify(a));
c.a.a = 2;

console.log(a === c); //false
console.log(a.a === c.a); //false
console.log(a.a.a === c.a.a); //false

console.log(c); // { a: { a: 2 } }
console.log(a); // { a: { a: 1 } }

然而我們在上面知道是因為第二層的物件沒有經過展開運算子的處理所以才讓他pass by reference,只有處理到第一層的物件而已。

那麼我們可以寫一個function來讓裡面不管有幾多層物件,都可以被處理才對。

自製深拷貝function

建立一個叫做deepClone的funcion

  • 先判斷這是是陣列還是物件,看outputObject要初始化陣列還是物件。
  • 然後使用for...in去迭代key
  • 判斷inputObject[key]是否等於物件,如果還是一個物件就再遞迴一次,讓他直到裡面不是物件的時候才跑到else去賦值給outputObject[key]
const a = {
  a: { a: 1 },
};


function deepClone(inputObject) {
  const outputObject = Array.isArray(inputObject) ? [] : {};

  for (const key in inputObject) {
    if (typeof inputObject[key] === "object") {
      outputObject[key] = deepClone(inputObject[key]);
    } else {
      outputObject[key] = inputObject[key];
    }
  }
  return outputObject;
}

const b = deepClone(a);
b.a.a = 2;

console.log(a === b); // false
console.log(a.a === b.a); //false
console.log(a.a.a === b.a.a); //false
console.log(a); // { a: { a: 1 } }
console.log(b); // { a: { a: 2 } }

這樣就算第二層也是物件也不會影響到原本的物件了,因為第二層的物件我們也已經再去做處理了。

這個實作看起來很複雜我懂!可是建議可以自己寫寫看會比較清楚他們之間的關係。

總結

  • 單純物件裡面的value是基本型別的話可以安心使用展開運算子、Object.assign()去拷貝一個一樣的物件。
  • 如果物件裡面的value又是一個物件的話再使用展開運算子..就會是淺拷貝,因為第二層不會被拷貝到,因為還是pass by reference。
  • 深拷貝是不管裡面是物件都不會影響,完完全全是兩個不同的物件,不會有pass by reference的問題。

今天就這樣拉!
當初剛唸完pass系列的時候想說休息一下感覺深拷貝淺拷貝很難,但最近在看的時候覺得也還好呀,所以能一鼓作氣就一次學完吧!

明天見了!


參考資料:JS 中的淺拷貝 (Shallow copy) 與深拷貝 (Deep copy) 原理與實作
如何写出一个惊艳面试官的深拷贝?
關於JS中的淺拷貝(shallow copy)以及深拷貝(deep copy)


上一篇
Day2 前端蛇行撞牆記 - 今天要來點 pass by value、pass by reference 還是 pass by sharing呢?
下一篇
Day4 前端蛇行撞牆記 - 讓你理解的 try...catch
系列文
前端蛇行撞牆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言