深拷贝循环引用问题

161 阅读2分钟

说到深拷贝我们想到最多的就是序列化和反序列化

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,从而实现了深拷贝。

然而,这种方法有一些限制:

  1. 不支持复杂对象:JSON.stringifyJSON.parse 只能处理纯数据的对象,对于包含函数、原型链等特殊情况的对象可能会出现问题。
  2. 无法处理循环引用:如果对象存在循环引用(即对象的属性之间相互引用导致的循环),使用这种方法会出现堆栈溢出或者序列化失败的情况。
  3. 不复制对象的原型链和函数:序列化和反序列化只复制对象的数据,不会复制对象的原型链和函数,因此可能会丢失部分信息和功能

原始方法实现深拷贝

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)

但是这样还有问题,因为这样回导致无限递归到爆栈,打印如下

image.png 为解决这个问题我们可以使用 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);