在 JavaScript 中,淺拷貝(shallow copy) 與 深拷貝(deep copy) 是物件或陣列複製時很重要的概念。以下是詳細的解說,並搭配常見題目與解答,幫助你徹底理解這兩者的差異。
🔍 1. 淺拷貝 vs 深拷貝 差異解說
✅ 淺拷貝(Shallow Copy)
淺拷貝:只複製第一層的屬性,如果屬性是物件或陣列的話,拷貝的是參考(reference),不是值本身。
常見方法:
Object.assign()
- 展開運算子
{ ...obj }
、[ ...arr ]
範例:
1 | const obj1 = { |
✅ 深拷貝(Deep Copy)
深拷貝:不僅拷貝第一層,還會遞迴地複製所有子屬性,即便是物件或陣列,也會整個拷貝成新的。
常見方法:
JSON.parse(JSON.stringify(obj))
(⚠️ 有限制,不能複製function
、undefined
、Symbol
等)- 使用 遞迴函式手動拷貝
- 使用第三方函式庫(如
lodash
的_.cloneDeep()
)
範例:
1 | const obj1 = { |
🧠 2. 常見面試題與解答
📌 題目 1:請解釋什麼是深拷貝與淺拷貝,並舉例說明差異?
解答:
淺拷貝是複製物件的第一層屬性,但如果屬性是物件,會複製的是參考;深拷貝則會遞迴複製所有屬性,完全拷貝一份新的物件。
範例:
1 | const obj = { a: 1, b: { c: 2 } }; |
📌 題目 2:寫一個遞迴的 deepClone 函數
解答:
1 | function deepClone(obj) { |
📌 題目 3:以下程式碼輸出是什麼?為什麼?
1 | const original = { a: 1, b: { c: 2 } }; |
答案:
1 | 99 |
原因: Object.assign
是淺拷貝,b
屬性是物件,所以複製的是 reference。修改 copy.b.c
等同於改了 original.b.c
。
🧰 延伸:使用 lodash 的 cloneDeep
1 | import _ from 'lodash'; |
🔚 總結對比表格
特性 | 淺拷貝 (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() |
資料共享會導致非預期變動,深拷貝可徹底分離資料 |