说到深拷贝我们想到最多的就是序列化和反序列化
const obj = {
arr: [],
a: 4,
};
// 将对象序列化为字符串
const serializedObj = JSON.stringify(obj);
// 将字符串反序列化为新的对象
const obj2 = JSON.parse(serializedObj);
console.log(obj2);
上面的代码中,JSON.stringify(obj) 将对象 obj 序列化为字符串,然后 JSON.parse(serializedObj) 将序列化后的字符串反序列化为新的对象 obj2,从而实现了深拷贝。
然而,这种方法有一些限制:
- 不支持复杂对象:
JSON.stringify和JSON.parse只能处理纯数据的对象,对于包含函数、原型链等特殊情况的对象可能会出现问题。 - 无法处理循环引用:如果对象存在循环引用(即对象的属性之间相互引用导致的循环),使用这种方法会出现堆栈溢出或者序列化失败的情况。
- 不复制对象的原型链和函数:序列化和反序列化只复制对象的数据,不会复制对象的原型链和函数,因此可能会丢失部分信息和功能
原始方法实现深拷贝
const obj = {
arr:[],
a:4,
}
obj.sub = obj
obj.arr.push(obj)
console.log(obj.arr.length) // 无限循环
const deepClone = (value) => {
if(typeof value !== 'object' || value === null){
return value
}
const target = Array.isArray(value) ? [] : {}
for(let key in value){
console.log(key); //打印如下图
target[key] = deepClone(value[key])
}
return target
}
const obj2 = deepClone(obj)
console.log(obj2)
但是这样还有问题,因为这样回导致无限递归到爆栈,打印如下
为解决这个问题我们可以使用
Map 数据结构来存储已经拷贝过的对象,如果检测到循环引用,则直接返回已经拷贝过的对象,避免了无限循环的问题,更改后代码如下
const obj = {
arr: [],
a: 4,
};
obj.sub = obj;
obj.arr.push(obj);
console.log(obj.arr.length); // 无限循环
const deepClone = (value, map = new Map()) => {
if (typeof value !== "object" || value === null) {
return value;
}
// 检查是否存在循环引用
if (map.has(value)) {
return map.get(value);
}
const target = Array.isArray(value) ? [] : {};
// 将当前对象和对应拷贝存入 Map,避免循环引用
map.set(value, target);
for (let key in value) {
if (Object.prototype.hasOwnProperty.call(value, key)) {
target[key] = deepClone(value[key], map);
}
}
return target;
};
const obj2 = deepClone(obj);
console.log(obj2);