淺層複製與深層複製

之前提到物件型別有傳參考的特性,但是如果想要另外修改資料,就會連帶影響原始資料內容,JavaScript 提供幾種複製方式來解決這個問題。


淺層複製 Shallow Copy

淺層複製只能複製物件的第一層,如果修改第二層資料的話,還是會影響原始資料。

for-in loop

以下範例可以發現,用 for-in 迴圈複製後的新物件會和原始資料不同。

1
2
3
4
5
6
7
8
9
10
11
var data = {
title: 'First Level',
info: {
tag: 'js'
}
}
var target = {}
for ( let key in data ) {
target[key] = data[key]
}
console.log(target !== data) // true

但是接著修改新物件後,修改第一層的內容沒有問題,不會影響原始資料,但是修改第二層後,就會連同原始物件一起修改。

1
2
3
target.title = 'Second Level' 
target.info.tag = 'copy'
console.log(target, data)

Image

Object.assign

Object.assign() 可以用來複製物件本身的屬性到另外一個目標物件上。

1
2
3
4
5
var origin = { a: 1, b: 2, c: 3 }
var target = Object.assign({}, origin)
console.log(origin, target)
// origin = { a: 1, b: 2, c: 3 }
// target = { a: 1, b: 2, c: 3 }

如果 目標物件原始物件 的屬性名稱相同,原始物件 的屬性會覆蓋 目標物件

1
2
3
4
5
6
var origin = { a: 1, b: 2, c: 3 }
var target = { a: 4, b: 5, x: 0 }
Object.assign(target, origin)
console.log(origin, target)
// origin = { a: 1, b: 2, c: 3 }
// target = {a: 1, b: 2, x: 0, c: 3}

ES6 展開語法

除了以上兩種方法,也可以用 ES6 的展開語法達到相同效果。

1
2
3
4
5
var data = { a: 1, b: 2, c: 3 }
var target = {...data}
target.a = 999
console.log(target)
// target = { a: 999, b: 2, c: 3 }

深層複製 Deep Copy

淺層複製只能複製第一層資料,深層複製則是可以將資料完整複製,讓整份新複製的資料都能往下層修改,並且不會影響原始資料。

JSON.stringify

JSON.stringify() 可以將物件轉成「字串」後回傳。
重新轉成字串後再用 JSON.parse() 轉回 JavaScript 物件,如此就能達到深層複製的效果。
原理是將物件先轉成「純值」,再透過 JSON.parse() 轉回物件,此時的物件就會有一個新的記憶體空間,並且讓變數指向到新的位置。

如果物件中有 undefined、function,因為無法轉成純值,所以會直接消失,NaN 則會變成 Null
1
2
3
4
5
6
7
8
9
10
var data = {
title: 'First Level',
info: {
tag: 'js'
}
}
var target = JSON.parse(JSON.stringify(data))
target.title = 'Second Level'
target.info.tag = 'copy'
console.log(target, data)

Image

jQuery.extend

如果要解決 undefinedfunctionNaN 的複製問題,可以用 jQuery 的 $.extend 方法,就能做到完整的深層複製。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var data = {
title: 'First Level',
info: {
tag: 'js'
},
func: function () {
console.log('data.func')
},
un: undefined,
nan: NaN
}
var shallow = $.extend({}, data) // 淺層複製
var deep = $.extend(true, {}, data) // 深層複製
console.log(deep)

參考文章