avatar

目錄
JavaScript-淺拷貝(Shallow Copy)&深拷貝(Deep Copy)

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

在 Javascript 裡面,分為原始型別(Primitive Type)及物件(Non-Primitive)(MDN)。

原始型別有:

  1. Number
  2. String
  3. Boolean
  4. Null
  5. Undefined
  6. Symbol(ES6)

物件有:

  1. Object
  2. Array(可參考)
  3. Function
  4. Date
  5. Regx(正規)
  6. Construct(建構式)

原始型別在賦值時一般是直接傳值(pass by value),如

Code
1
2
3
4
5
6
let a = 1;
let b = a;
b = 10

console.log(a) //1
console.log(b) //10

物件會是傳參考(pass by Reference,變數的記憶體位址),如

Code
1
2
3
4
5
6
7
8
9
let a = {
id: 0
}

let b = a
a.id = 2

console.log(b.id) // 2 改 a 卻影響 b
// 因為物件 a 與物件 b 關聯的是同一個記憶體位址

為了達成修改 a 物件底下的屬性時,而不會影響到 b 物件底下的屬性值的這件事。所以一般我們在複製物件(or 陣列)時會利用函式來處理,而不會直接用=賦值。

而複製又分為淺拷貝(Shallow Copy)&深拷貝(Deep Copy)

淺拷貝:
就是物件指向某個指標(Node,只有一層),但還是共用同一塊記憶體。
深拷貝:
就是另外建立新的物件,有同樣的內容,但不共用記憶體位址。

淺拷貝(Shallow Copy)

物件只有一層時使用,像以下兩種情況。

arr = [1,2,3]

or

Code
1
2
3
let a = {
id: 0
}

陣列

可用 slice(0)來做處理。

Array.concat()也是淺拷貝。

e.g.

Code
1
2
3
4
5
6
let arr1 = [1,2,3];
let arr2 = arr1.slice(0);

arr2[0] = 42;
arr1; // [1, 2, 3]
arr2; // [42, 2, 3]

物件

ES6 有 Object.assign() 的方法就可以進行淺拷貝。

Object.assign():會將一個或多個物件進行合併,並回傳一個 全新的物件參考

e.g.

Code
1
2
3
4
5
6
7
8
let a = {
id: 0
}
// 先建立一個空物件,再將 a 屬性值複製過去,簡單的需求下使用。
let b = Object.assign({}, a)

a.id = 2
console.log(b.id) // 0

深拷貝(Deep Copy)

物件有多層時使用。利用JSON.stringify()先把整個物件變成字串,再藉由JSON.parse()轉回來物件的型態,此方法可以完全複製整個原型鏈達到深拷貝(Deep Copy)。

陣列

若是陣列裡的「元素」是「物件型別」的陣列。使用slice(0)的方式。並沒有辦法避免互相影響。

e.g.

Code
1
2
3
4
5
6
7
8
9
10
let arr1 = [[1,2,3],[4,5,6]];
let arr2 = arr1.slice(0);

arr2[0][0] = 42;
arr1;
// 0: (3) [42, 2, 3]
// 1: (3) [4, 5, 6]
arr2;
// 0: (3) [42, 2, 3]
// 1: (3) [4, 5, 6]

此時改用深拷貝。

e.g.

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function DeepClone(obj) {
// 原本的物件值轉成字串,然後再轉回來物件的型態
return JSON.parse(JSON.stringify(obj));
}
let arr1 = [[1,2,3],[4,5,6]];
let arr2 = DeepClone(arr1);

arr2[0][0]=42;
arr1;
// 0: (3) [1, 2, 3]
// 1: (3) [4, 5, 6]
arr2;
// 0: (3) [42, 2, 3]
// 1: (3) [4, 5, 6]

達成不互相影響的要求。

物件

同樣先用Object.assign()來試試看。

e.g.

Code
1
2
3
4
5
6
7
8
9
10
let a = {
name: {
first: 'HUANG',
last: 'Roxas'
}
}
let b = Object.assign({}, a)
a.name.first = 'LI'
console.log(b.name.first) // LI,改 a 卻影響 b
// 因為在 a 物件底下的 name 又獨立有了一個物件,進行淺拷貝(Shallow Copy)時,並沒有將此物件拷貝並建立新的關聯。

改用JSON.parseJSON.stringify的方式來做。

e.g.

Code
1
2
3
4
5
6
7
8
9
let a = {
name: {
first: 'HUANG',
last: 'Roxas'
}
}
let b = JSON.parse(JSON.stringify(a))
a.name.first = 'LI'
console.log(b.name.first) // HUANG

另外 jQuery 中的 $.extend 方法,與 lodash _.cloneDeep 方法,也都可以用來實作深拷貝(Deep Copy)。

補充:
在 JavaScript 中
{} === {} & {} == {} // return false
不相等的原因是,這兩個物件實字存放於兩個不同的記憶體空間位置。

實字與建構式

應該盡量避開建構式來建立物件或陣列,實字模式更加簡潔、更不易出錯。

物件實字:用 {} 建立
e.g.
let car = {Brand:"Toyota"}

物件建構式:new object()
e.g.
let car = new object();
car.Brand = "Toyota";

陣列實字:用 []建立
e.g.
let arr = [1,2,3];

陣列建構式:new Array()
e.g.
let arr = new Array(1,2,3);

參考:
https://dustinhsiao21.com/2018/01/07/javascript-shallow-copy-and-deep-copy/
http://skyroxas.tw/javascript-實作技巧:-淺拷貝shallow-copy-深拷貝deep-copy/
https://joansay.blogspot.com/2017/12/javascript_14.html
https://ithelp.ithome.com.tw/articles/10221481?sc=rss.iron
https://medium.com/schaoss-blog/前端三十-14-js-深拷貝是什麼-如何實現-a31841fccc9b