深度拷贝(Deep Copy)实现详解
深度拷贝创建一个完全独立的新对象,递归复制原始对象的所有嵌套对象,使得新旧对象完全独立,互不影响。
1. JavaScript/TypeScript 实现
方法一:JSON方法(最简单但有局限性)
js
const original = { a: 1, b: { c: 2, d: [3, 4] } };
const deepCopy = JSON.parse(JSON.stringify(original));
局限性:
- 不能复制函数、undefined、Symbol
- 不能处理循环引用
- 会忽略原型链
- Date对象会变成字符串
方法二:递归实现(最完整)
js
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);
// 处理Array对象
if (Array.isArray(obj)) {
const arrCopy = [];
hash.set(obj, arrCopy); // 存储已拷贝的对象,处理循环引用
obj.forEach((item, index) => {
arrCopy[index] = deepClone(item, hash);
});
return arrCopy;
}
// 处理普通对象
if (typeof obj === 'object') {
// 如果已经拷贝过,直接返回(处理循环引用)
if (hash.has(obj)) return hash.get(obj);
const objCopy = {};
hash.set(obj, objCopy);
// 复制所有属性,包括Symbol属性
const allKeys = [
...Object.getOwnPropertyNames(obj),
...Object.getOwnPropertySymbols(obj)
];
for (const key of allKeys) {
objCopy[key] = deepClone(obj[key], hash);
}
return objCopy;
}
// 其他情况(如函数)直接返回
return obj;
}
// 使用示例
const original = {
name: 'Alice',
scores: [85, 90, 95],
address: {
city: 'Beijing',
street: 'Main St'
},
getInfo() {
return `${this.name} from ${this.address.city}`;
},
date: new Date(),
regex: /test/gi
};
original.self = original; // 循环引用
const deepCopy = deepClone(original);
方法三:使用structuredClone API(现代浏览器)
js
// 支持大多数类型,包括循环引用
const original = { a: 1, b: { c: 2 } };
const deepCopy = structuredClone(original);
// 检查是否支持
if (typeof structuredClone === 'function') {
// 可以使用
}
方法四:使用第三方库
js
// lodash
import _ from 'lodash';
const deepCopy = _.cloneDeep(original);
// Ramda
import * as R from 'ramda';
const deepCopy = R.clone(original);
2. 不同场景下的深拷贝选择
性能对比(JavaScript示例)
js
// 测试不同方法的性能
const testObj = {
data: Array(1000).fill(0).map((_, i) => ({
id: i,
nested: { value: Math.random() },
arr: Array(10).fill(Math.random())
}))
};
// JSON方法(最快,但有局限性)
console.time('JSON');
const copy1 = JSON.parse(JSON.stringify(testObj));
console.timeEnd('JSON');
// 递归方法
console.time('Recursive');
const copy2 = deepClone(testObj);
console.timeEnd('Recursive');
// structuredClone
console.time('structuredClone');
const copy3 = structuredClone(testObj);
console.timeEnd('structuredClone');
选择指南
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JSON方法 | 简单、快速 | 丢失函数、特殊对象 | 纯数据对象,无特殊类型 |
| 递归实现 | 最完整,可扩展 | 实现复杂,性能较低 | 需要完整复制,包含特殊类型 |
| structuredClone | 原生支持,性能好 | 浏览器兼容性 | 现代Web应用 |
| 第三方库 | 稳定,功能丰富 | 增加依赖 | 企业级应用 |
3. 深拷贝的注意事项
- 循环引用处理:必须检测并处理循环引用
- 特殊对象类型:Date、RegExp、Map、Set、函数等
- 原型链继承:是否保持原型链
- 不可枚举属性:是否复制不可枚举属性
- Symbol属性:是否复制Symbol作为键的属性
- 性能优化:对于大对象,需要考虑性能问题
4. 最佳实践
js
// 生产环境推荐:使用经过测试的库
// 对于简单需求:JSON.parse(JSON.stringify(obj))
// 对于复杂需求:使用lodash的_.cloneDeep或自己实现完整递归
// 安全考虑:处理不可信数据时的深拷贝
function safeDeepClone(obj) {
// 1. 验证对象大小
if (JSON.stringify(obj).length > 10 * 1024 * 1024) { // 10MB限制
throw new Error('Object too large for deep cloning');
}
// 2. 使用JSON方法(避免原型链攻击)
const cloned = JSON.parse(JSON.stringify(obj));
// 3. 验证结果
// ... 这里可以添加验证逻辑
return cloned;
}
深拷贝是实现数据隔离的重要手段,选择合适的方法需要根据具体需求、性能要求和环境限制来决定。对于大多数应用场景,使用成熟的第三方库是最稳妥的选择。