为什么需要深拷贝?
首先我们看看浅拷贝,point指向的是同一个地址,这时我们修改obj2.point的属性时,obj1的point属性也会被修改
再看看深拷贝,point指向的是不同地址,这时我们修改obj2.point的属性时,obj1的point属性不受影响
总结一下深拷贝的特点:
- 避免数据污染:修改副本时不影响原始数据
- 解决嵌套问题:确保对象中的子对象被独立复制
- 处理循环引用:当对象相互引用时不导致程序崩溃
三种深拷贝实用解决方案
1. JSON序列化法(适合简单数据)
const copy = JSON.parse(JSON.stringify(original));
优点:实现简单,一行代码搞定
局限:会丢失函数、Symbol等特殊对象类型
2. Lodash库方案(生产环境推荐)
import { cloneDeep } from 'lodash';
const copy = cloneDeep(original);
推荐理由:完整支持所有数据类型,经过大量项目验证
3. 手动实现方案(完全掌控型)
/**
* 深度克隆对象
* @param source 需要克隆的对象
* @param weakMap 弱引用映射表用于处理循环引用(可选)
* @returns 克隆后的新对象
*/
function deepClone<T>(source: T, weakMap = new WeakMap<any, any>()): T {
// 处理基本数据类型和非对象类型
if (
source === null ||
typeof source !== 'object' ||
source instanceof Function
) {
return source;
}
// 特殊对象处理
if (source instanceof Date) return new Date(source);
if (source instanceof RegExp) return new RegExp(source);
// 处理循环引用
if (weakMap.has(source)) {
return weakMap.get(source);
}
const proto = Object.getPrototypeOf(source);
const clone = Object.create(proto);
weakMap.set(source, clone);
// 克隆所有自有属性(包括Symbol和不可枚举属性)
const allKeys = Reflect.ownKeys(source as object);
for (const key of allKeys) {
const descriptor = Object.getOwnPropertyDescriptor(source as object, key)!;
if ('value' in descriptor) {
// 克隆值属性
Object.defineProperty(
clone,
key,
Object.assign(descriptor, {
value: deepClone((source as any)[key], weakMap)
})
);
} else {
// 复制访问器属性(getter/setter)
Object.defineProperty(clone, key, descriptor);
}
}
// 保持原型链完整性
if ((source as any).__proto__ !== Object.prototype) {
clone.__proto__ = (source as any).__proto__;
}
return clone;
}
关键技术要点
- 循环引用:通过WeakMap缓存已拷贝对象
- 函数处理:直接引用而非复制(保持函数特性)
- 特殊对象:Date/RegExp等需调用构造函数
- 原型链:使用Object.create保持原型关系