mqyqingfeng / Blog

冴羽写博客的地方,预计写四个系列:JavaScript深入系列、JavaScript专题系列、ES6系列、React系列。
30.53k stars 4.7k forks source link

深拷贝要注意存在循环引用的问题 #265

Open 18127443932 opened 2 years ago

18127443932 commented 2 years ago

先来看看例子:

function deepCopy(obj) {
  if(typeof obj !== 'object') return obj
  if(obj === null) return null
  if(obj.constructor === Date) return new Date(obj)
  if(obj.constructor === RegExp) return new RegExp(obj)

  const res = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (typeof obj[key] === "object") {
      res[key] = deepCopy(obj[key]);
    } else {
      res[key] = obj[key];
    }
  }
  return res;
}
var obj = {
  a: 1,
  b: 2,
  c: [1, 2, 3],
  d: { aa: 1, bb: 2 },
};
obj.e = obj;
console.log("obj", obj); // 不会报错

const objCopy = deepCopy(obj);
console.log(objCopy); //Uncaught RangeError: Maximum call stack size exceeded

从例子可以看到,当存在循环引用的时候,deepCopy 会报错,栈溢出。

循环应用问题解决

即:目标对象存在循环应用时报错处理大家都知道,对象的 key 是不能是对象的。

{{a:1}:2}
// Uncaught SyntaxError: Unexpected token ':'

解决办法一

使用weakmap 解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系 这个存储空间,需要可以存储 key-value 形式的数据,且 key 可以是一个引用类型,我们可以选择 weakMap 这种数据结构:

  1. 检查 weakMap 中有无克隆过的对象。
  2. 有,直接返回
  3. 没有,将当前对象作为 key,克隆对象作为 value 进行存储
  4. 继续克隆
function deepClone(obj, hash = new WeakMap()) {
    if(typeof obj !== 'object') return obj
    if(obj === null) return null
    if(obj.constructor === Date) return new Date(obj)
    if(obj.constructor === RegExp) return new RegExp(obj)
    if(hash.has(obj)) return hash.get(obj)

    let target = Array.isArray(obj) ? [] : {}
    hash.set(obj, target)

    Object.keys(obj).forEach(item => {
      if (isObj(obj[item])) {
        target[item] = deepClone(obj[item], hash)
      } else {
        target[item] = obj[item]
      }
    })
    return target
  }
  // test
  var a = {
    a: 123,
    b: isObj,
    c: [1,2,3,4,5, { a: 123}],
    d: undefined
  }
  a.e = a
  var b = deepClone(a)
  console.log(`b`, b)