JS-淺拷貝(Shallow Copy) VS 深拷貝(Deep Copy)

淺拷貝(Shallow Copy) VS 深拷貝(Deep Copy)

基本與物件型別傳值的差異

JavaScript 內建的型別主要可以分成基本型別 (Primitives)物件型別 (Object) 兩大類。

而基本型別又分成 stringnumberbooleannullundefined 幾種,
除了以上幾種之外,其他都可以歸類至物件型別 (Object)

這二種型別之間的差異,就是在他們的傳值方式

  • 基本型別 => 傳(value)
  • 物件型別 => 傳(reference)

下面範例來解說他們的差異

基本型別

基本型別是傳 value

基本型別,傳value
1
2
3
4
5
6
7
let a = "爸爸";
let b = a;

b = "媽媽";

console.log(a); //爸爸
console.log(b); //媽媽

在修改 b 時並不會改到 a 的值

物件型別

物件就不同,物件傳的是 reference

物件型別,傳reference
1
2
3
4
5
6
7
let objA = { name: '王大頭' }
let objB = objA

objB.name = '盧卡斯'

console.log(objA); //"盧卡斯"
console.log(objB); //"盧卡斯"

淺拷貝(Shallow Copy) VS 深拷貝(Deep Copy)

  • 拷貝:
    只能達到淺層的複製(第一層),若有第二層以上的資料的話,
    就無法達到實際的複製,而是會與舊物件一起共用同一塊記憶體。

  • 拷貝:
    會另外創造一個一模一樣的物件,新物件跟原物件共用記憶體,修改新物件不會改到原物件。


Object.assign

Object.assign 是 ES6 的新函式,我們可以用來達成複製的功能

複製物件
1
2
3
4
5
6
7
8
9
let obj = {name: '王康寶', age:{child: 18}}
let copy = Object.assign({}, obj);

//更改 copy.name 的值
copy.name = '盧卡斯';

//輸出
console.log(obj); //{name: "王康寶", age:{child: 18}}
console.log(copy); //{name: "盧卡斯", age:{child: 18}}

But Object.assign 並不是那麼的完美,請再看下例

淺拷貝
1
2
3
4
5
6
7
8
9
let obj = {name: '王康寶', age:{child: 18}}
let copy = Object.assign({}, obj);

copy.name = '盧卡斯';
copy.age.child = 99; //更改 copy.age.child 的值

//輸出
console.log(obj); //{name: "王康寶", age:{child: 99}}
console.log(copy); //{name: "盧卡斯", age:{child: 99}}

我們可以看到更改 copy.age.child 值以後,發現 obj.age.child 值也跟著被變掉了,
所以 Object.assign 能處理深度,只有一層的物件,沒辦法做到真正的 深拷貝(Deep Copy)
不過如果要複製的物件只有一層的話可以考慮使用他。

另外也可以使用 展開運算子(Spread Operator) 達成複製,不過一樣是 淺層的複製

展開運算子( Spread Operator )
1
2
3
4
5
6
7
8
9
let obj = {name: '王康寶', age:{child: 18}}
let copy = {...obj}; //展開運算子(Spread Operator)

copy.name = '盧卡斯';
copy.age.child = 99; //更改 copy.age.child 的值

//輸出
console.log(obj); //{name: "王康寶", age:{child: 99}}
console.log(copy); //{name: "盧卡斯", age:{child: 99}}

深拷貝的作法

jQuery

jquery 有提供一個 $.extend 可以用來做 Deep Copy

jquery.extend
1
2
3
4
5
6
7
8
9
let obj = {name: '王康寶', age:{child: 18}}
let copy = $.extend(true, {}, obj); //使用 jquery.extend

copy.name = '盧卡斯';
copy.age.child = 99; //更改 copy.age.child 的值

//輸出
console.log(obj); //{name: "王康寶", age:{child: 18}}
console.log(copy); //{name: "盧卡斯", age:{child: 99}}

lodash

lodash 也有提供 _.cloneDeep 用來做 Deep Copy

lodash.cloneDeep
1
2
3
4
5
6
7
8
9
let obj = {name: '王康寶', age:{child: 18}}
let copy = _.cloneDeep(obj); //使用 lodash.cloneDeep

copy.name = '盧卡斯';
copy.age.child = 99; //更改 copy.age.child 的值

//輸出
console.log(obj); //{name: "王康寶", age:{child: 18}}
console.log(copy); //{name: "盧卡斯", age:{child: 99}}

參考來源


FB討論串

2018/04/26 社團剛好有人詢問此問題,也有大大回覆一些見解,留個紀錄可以回頭查。

簡單重點整理:

  • 可用 JSON.parse(JSON.stringify({})) 偽深拷貝
    • 純資料,可行。
    • 若遇 Function、Set、Map..等型態,失效。
  • 為何要複製function? 目的?
    • 站在節省記憶體的角度,function能重複利用就重複利用
    • 若是要寫物件導向風格
      • ES5:寫 Function + prototype
      • ES6:寫 Class

JSON複製範例

JSON複製範例
1
2
let a = {o:{v:1}}
let b = JSON.parse(JSON.stringify(a));