Javascript深拷貝淺拷貝作法

在 JavaScript 中,淺拷貝(shallow copy)深拷貝(deep copy) 是物件或陣列複製時很重要的概念。以下是詳細的解說,並搭配常見題目與解答,幫助你徹底理解這兩者的差異。


🔍 1. 淺拷貝 vs 深拷貝 差異解說

✅ 淺拷貝(Shallow Copy)

淺拷貝:只複製第一層的屬性,如果屬性是物件或陣列的話,拷貝的是參考(reference),不是值本身。

常見方法:

  • Object.assign()
  • 展開運算子 { ...obj }[ ...arr ]

範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
const obj1 = {
name: 'Tom',
info: {
age: 25
}
};

const shallowCopy = { ...obj1 };

shallowCopy.name = 'Jerry'; // ✅ 不會影響原物件
shallowCopy.info.age = 30; // ⚠️ 原物件也會被改變!

console.log(obj1.info.age); // 30(被改到)

✅ 深拷貝(Deep Copy)

深拷貝:不僅拷貝第一層,還會遞迴地複製所有子屬性,即便是物件或陣列,也會整個拷貝成新的。

常見方法:

  • JSON.parse(JSON.stringify(obj))(⚠️ 有限制,不能複製 functionundefinedSymbol 等)
  • 使用 遞迴函式手動拷貝
  • 使用第三方函式庫(如 lodash_.cloneDeep()

範例:

1
2
3
4
5
6
7
8
9
10
11
12
const obj1 = {
name: 'Tom',
info: {
age: 25
}
};

const deepCopy = JSON.parse(JSON.stringify(obj1));

deepCopy.info.age = 30;

console.log(obj1.info.age); // ✅ 仍為 25(原物件沒變)

🧠 2. 常見面試題與解答

📌 題目 1:請解釋什麼是深拷貝與淺拷貝,並舉例說明差異?

解答:

淺拷貝是複製物件的第一層屬性,但如果屬性是物件,會複製的是參考;深拷貝則會遞迴複製所有屬性,完全拷貝一份新的物件。

範例:

1
2
3
4
5
6
7
8
9
const obj = { a: 1, b: { c: 2 } };
const shallow = { ...obj };
const deep = JSON.parse(JSON.stringify(obj));

shallow.b.c = 99;
console.log(obj.b.c); // 99,說明是淺拷貝

deep.b.c = 100;
console.log(obj.b.c); // 99,不變,說明是深拷貝

📌 題目 2:寫一個遞迴的 deepClone 函數

解答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;

if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}

const result = {};
for (const key in obj) {
result[key] = deepClone(obj[key]);
}

return result;
}

// 測試
const obj1 = { a: 1, b: { c: 2 } };
const clone = deepClone(obj1);
clone.b.c = 99;

console.log(obj1.b.c); // 2(沒被改變)

📌 題目 3:以下程式碼輸出是什麼?為什麼?

1
2
3
4
const original = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, original);
copy.b.c = 99;
console.log(original.b.c);

答案:

1
99

原因: Object.assign 是淺拷貝,b 屬性是物件,所以複製的是 reference。修改 copy.b.c 等同於改了 original.b.c


🧰 延伸:使用 lodash 的 cloneDeep

1
2
3
4
5
6
7
import _ from 'lodash';

const obj = { a: 1, b: { c: 2 } };
const deepCopy = _.cloneDeep(obj);
deepCopy.b.c = 99;

console.log(obj.b.c); // 2

🔚 總結對比表格

特性 淺拷貝 (Shallow Copy) 深拷貝 (Deep Copy)
複製層級 僅第一層 所有層級
參考關係 內層物件是參考 完全複製
方法 Object.assign... 遞迴函數、cloneDeep
安全性 修改內層會影響原物件 修改不會影響原物件

深淺拷貝最佳實踐

目的 / 場景 建議做法 使用方式 原因
只複製一層簡單資料(如物件無巢狀結構) ✅ 淺拷貝 Object.assign({}, obj){ ...obj } 效能高、語法簡潔,適用於扁平資料
複製陣列的第一層元素(無巢狀) ✅ 淺拷貝 [...arr]arr.slice() 常見陣列操作,快速又直觀
資料包含巢狀物件或多層結構,需要完全脫鉤原資料 ✅ 深拷貝 JSON.parse(JSON.stringify(obj))(限純資料)
或自寫 deepClone() 遞迴函數
或使用 lodash.cloneDeep()
淺拷貝無法處理巢狀結構,會有參考共享風險
資料包含函式、undefined、Symbol 等特殊值 ✅ 使用 lodash.cloneDeep() 或自訂 deepClone _.cloneDeep(obj) 或自定義深拷貝函式支援各型別 JSON.parse 方式會遺失特殊型別,導致資料不完整
效能敏感場景下(大型資料結構)不需完全複製 ✅ 淺拷貝 + 不變資料原則 只做淺拷貝後避免修改巢層資料,例如 { ...obj } 然後不改動 obj.b 等巢層 深拷貝成本高,若可避免修改參考資料則淺拷貝更有效率
需避免資料共享或引用污染(多人共用資料來源) ✅ 深拷貝 使用自寫 deepClone()lodash.cloneDeep() 資料共享會導致非預期變動,深拷貝可徹底分離資料