在 JavaScript 中,深拷貝(Deep Copy)和淺拷貝(Shallow Copy)是處理對象複製的兩種不同方式:
一、淺拷貝(Shallow Copy)
只複製對象的第一層屬性,如果屬性是引用類型,則複製的是引用地址。
實現方式:
// 1. 擴展運算符
const obj = { a: 1, b: { c: 2 } };
const shallowCopy1 = { ...obj };
// 2. Object.assign()
const shallowCopy2 = Object.assign({}, obj);
// 3. 數組的淺拷貝方法
const arr = [1, 2, { a: 3 }];
const shallowArr1 = arr.slice();
const shallowArr2 = arr.concat();
const shallowArr3 = [...arr];
// 4. Array.from()
const shallowArr4 = Array.from(arr);
問題:
const original = {
name: "John",
address: { city: "New York" }
};
const shallow = { ...original };
shallow.address.city = "London";
console.log(original.address.city); // "London"(原對象也被修改了)
二、深拷貝(Deep Copy)
完全複製對象及其嵌套對象,新舊對象互不影響。
實現方式:
1. JSON 方法(最簡單但有限制)
const obj = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(obj));
侷限性:
- 不能複製函數
- 不能複製
undefined - 不能複製 Symbol
- 不能處理循環引用
- 會丟失 Date、RegExp 等特殊對象的構造函數
2. 手寫深拷貝函數
function deepClone(obj, hash = new WeakMap()) {
// 處理基本類型和 null
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 處理 Date
if (obj instanceof Date) {
return new Date(obj);
}
// 處理 RegExp
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// 處理數組
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item, hash));
}
// 處理普通對象
const clonedObj = {};
// 使用 WeakMap 解決循環引用
if (hash.has(obj)) {
return hash.get(obj);
}
hash.set(obj, clonedObj);
// 遞歸複製所有屬性
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = deepClone(obj[key], hash);
}
}
// 處理 Symbol 屬性
const symbolKeys = Object.getOwnPropertySymbols(obj);
for (let symKey of symbolKeys) {
clonedObj[symKey] = deepClone(obj[symKey], hash);
}
return clonedObj;
}
// 使用示例
const obj = {
name: "John",
address: { city: "New York" },
date: new Date(),
arr: [1, 2, { a: 3 }],
func: function() { console.log("hello"); },
[Symbol('id')]: 123
};
const cloned = deepClone(obj);
3. 使用第三方庫
// lodash
const _ = require('lodash');
const cloned = _.cloneDeep(obj);
// jQuery
const cloned = jQuery.extend(true, {}, obj);
三、性能對比
- 淺拷貝:性能最好,內存佔用少
- JSON 方法:中等性能,不能處理所有類型
- 遞歸深拷貝:性能較差,但功能最完整
- 第三方庫:功能完整,但需要額外引入
四、選擇建議
-
使用淺拷貝的場景:
- 對象結構簡單,沒有嵌套對象
- 只需要複製第一層數據
- 性能要求高
-
使用深拷貝的場景:
- 對象有深層嵌套結構
- 需要完全獨立的副本
- 需要修改副本而不影響原對象
五、現代 API
ES2023 引入了 structuredClone():
// 瀏覽器原生支持的深拷貝
const cloned = structuredClone(obj);
支持:基本類型、對象、數組、Map、Set、Date、RegExp、ArrayBuffer 等 不支持:函數、DOM 節點、Error 對象
六、注意事項
// 1. 循環引用問題
const obj = { a: 1 };
obj.self = obj;
// JSON.stringify(obj) 會報錯
// 2. 原型鏈上的屬性
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {};
const john = new Person('John');
// 淺拷貝不會複製原型鏈上的方法
根據具體需求選擇合適的拷貝方式,對於日常使用,可以先考慮 structuredClone()(現代瀏覽器)或 lodash 的 _.cloneDeep()。