【JS大哉問#1】淺拷貝(shallow copy) & 深拷貝(deep copy)

2023-06-05
JS大哉問

拷貝資料可分為一般型別的複製 (數字、字串) ,以及物件型別的複製 (陣列、物件) ,在進行一般型別的複製時可以在複製後輕易改動複製來的資料而不會連帶影響原本的資料:

1
2
3
4
let a=10;
let b=a;
b=99;
console.log(a) //a=10

但是當我們在做物件的複製時,情況會和一般型別複製不太一樣,先看以下例子:

1
2
3
4
let a=[1,2,3]
let b=a
b[0]=999
console.log(a) //[999,2,3]
1
2
3
4
let a={name:"Bob",age:21,interest:"jogging"}
let b=a
b.name="Sam"
console.log(a) //{name:"Sam",age:21,interest:"jogging"}

可以發現到把複製過來的b資料做修改時,a的資料竟然也跟著改動了
因此,當我們在做物件資料的拷貝時,深拷貝和淺拷貝的用意就在於「即便更動複製過來的資料也不會影響原本的那份資料」

淺拷貝

1. 其餘運算

可以用 [...要複製的陣列]、{...要複製的物件}的方式來拷貝,這麼一來即便改動複製過來的資料也不會連帶影響到原本的資料:

1
2
3
4
let a={x:1,y:2,z:3}
let b={...a}
b.x=999
console.log(a) // {x: 1, y: 2, z: 3}

乍看之下好像已經解決了我們原先的問題,但這裡還有一個地雷

1
2
3
4
5
let a={x:1,y:2,data:{name:"Bob",age:21,interest:"jogging"}}
let b={...a}
b.data.name="Sam"
console.log(a.data.name) //Sam, a的Bob又因為b的改動變成Sam了

只能拷貝第一層,當我們的資料有第二、第三層以上的時候,還是會因為改動而互相影響,就叫做淺拷貝

2. Object.assign()

Object.assign(陣列或物件括號, 要複製的資料)

1
2
3
4
let a={x:1,y:2,z:3}
let b=Object.assign({},a)
b.x=999
console.log(a) // {x: 1, y: 2, z: 3}

但一樣到了第二層的資料就會變成傳址(passing by reference)

1
2
3
4
let a={x:1,y:2,data:{name:"Bob",age:21,interest:"jogging"}}
let b=Object.assign({},a)
b.data.name="Sam"
console.log(a.data.name) //Sam, a的Bob又因為b的改動變成Sam了

深拷貝

既然淺拷貝只能複製第一層的資料,那深拷貝就是不管有幾層資料都能完整拷貝,且不管怎麼更動複製過來的資料都不影響原本的資料

1. JSON.stringify() & JSON.parse()

先把要複製過來的資料用JSON.stringify字串化,複製完再用JSON.parse變回原本的物件型態

JSON.stringify(要複製的物件)
JSON.parse(將複製好的資料物件化)

1
2
3
4
5
let a={x:1,y:2,data:[1,2,3]}
let b=JSON.stringify(a)
b=JSON.parse(b)
b.data[0]=999
console.log(a.data[0]) //1

可以發現,經過JSON格式把物件變成字串複製過來再變回物件後,即使將 b 物件所包覆的data的第一筆資料改成 999 ,也不會影響原本 a 物件所包覆的 data 的第一筆的 1

JSON.stringify() & JSON.parse()缺點: 複製過來的function無法呼叫

抱錯例子:

1
2
3
4
5
6
7
let a={x:1,y:2,data:function(){
console.log("HELLO!!")
}}
let b=JSON.stringify(a)
b=JSON.parse(b)
console.log(b.data)
b.data() // Uncaught TypeError: b.data is not a function

2. _.cloneDeep() 引用函式庫

lodash 是JavaScript的函式庫,裡面提供了一個深拷貝方法,讓拷貝過來的function可以被呼叫

1
2
3
4
5
let a={x:1,y:2,data:function(){
console.log("HELLO!!")
}}
let b=_.cloneDeep(a)
b.data() //HELLO!!

參考資料


留言: