JavaScript 核心觀念(30)-物件-淺層複製及深層複製

前言

接下來讓我們來了解什麼是淺層複製與深層複製吧。

淺層複製與深層複製

前面那麼多章節我們都明白了物件有所謂傳參考問題,簡單來講就是傳入一個物件,修改裡面的屬性會影響原有的物件資料,而這個狀況其實在開發上會引發相當多的 bug,舉凡來講我們想保留原始的物件資料,但是卻因為我們搞不清楚如何處理,那麼就會發生修改到原始資料,例如下面的範例

1
2
3
4
5
6
7
8
var b = {
name: 'Ray',
};

var e = b;

e.name = 'QQ';
console.log(b.name); // QQ

這邊的運作方式允許我貼先前的圖

記憶體指向動畫

那我們該如何解決這個問題呢?基本上解決方式滿多的,舉例來講使用 Object.assign 來達到拷貝一個新的物件

1
2
3
4
5
6
7
8
9
var b = {
name: 'Ray',
};

var e = Object.assign({}, b);

e.name = 'QQ';
console.log(b.name); // Ray
console.log(e.name); // QQ

除此之外也可以使用 ES6 的展開語法達到相同效果

1
2
3
4
5
6
7
8
9
var b = {
name: 'Ray',
};

var e = { ...b };

e.name = 'QQ';
console.log(b.name); // Ray
console.log(e.name); // QQ

但是這邊要注意一件事情,不論是 Object.assign 或是 ES6 展開語法,通通都是屬於 淺層拷貝,什麼是淺層拷貝呢?簡單來講就是這個物件底下還有另一個物件的時候還是會修改到原始物件,例如以下程式碼

1
2
3
4
5
6
7
8
9
10
11
12
var b = {
name: 'Ray',
obj: {
name: 'Two'
}
};

var e = Object.assign({}, b);

e.obj.name = 'Tree';
console.log(b.obj.name); // Tree
console.log(e.obj.name); // Tree

就算你使用 ES6 展開的語法也是一樣的

1
2
3
4
5
6
7
8
9
10
11
12
var b = {
name: 'Ray',
obj: {
name: 'Two'
}
};

var e = {...b}

e.obj.name = 'Tree';
console.log(b.obj.name); // Tree
console.log(e.obj.name); // Tree

在維基百科上其實也有說明 Object.assign 並不是一個深層複製的語法

深層複製(deep clone)需要使用其他的替代方案,因為 Object.assign() 僅複製屬性值。若來源物件的值參照到一個子物件,它只會複製該子物件的參照。

那麼該如何解決這個問題呢?其實最簡單方式就是使用 JSON.stringify,並搭配 JSON.parse

1
2
3
4
5
6
7
8
9
10
11
12
var b = {
name: 'Ray',
obj: {
name: 'Two'
}
};

var e = JSON.parse(JSON.stringify(b));

e.obj.name = 'Tree';
console.log(b.obj.name); // Two
console.log(e.obj.name); // Tree

這是一個最簡單解決淺層拷貝的方式,這稱之為深層拷貝,其運作原理是將物件轉換為純值,也就是 string 之後再轉回一個物件,此時的物件就會是新的記憶體空間。

但是使用 JSON.stringify,並搭配 JSON.parse 的方式來做深層拷貝其實是有一個很大的問題,只要你的物件中有 undefinedNaN 或是 function 就無法生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var b = {
name: 'Ray',
obj: {
name: 'Two'
},
fn: function() { // 消失
console.log('123');
},
un: undefined, // 消失
nan: NaN, // 直接變成 null
};

var e = JSON.parse(JSON.stringify(b));

console.log(e);

那麼在此如果想要解決這個問題的話,基本上會建議使用框架,例如 jQuery 的 $.extend 就可以做到完整的深層拷貝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var b = {
name: 'Ray',
obj: {
name: 'Two'
},
fn: function() {
console.log('123');
},
un: undefined,
nan: NaN,
};

var e = $.extend(true, {}, b);

console.log(e);

參考文獻