[JS基础] 深拷贝-循环引用处理

1,693 阅读1分钟

目录

  1. 深拷贝-循环引用处理 weakMap 写法一
  2. 深拷贝-循环引用处理 weakMap 写法二
  3. 递归-有循环引用的问题
  4. 递归-解决了循环引用

一、深拷贝-循环引用处理 weakMap 写法一


function deepCopy(obj) {
  // hash表,记录所有的对象的引用关系
  let map = new WeakMap();
  
  function dp(obj) {
    let result = null;
    let keys = Object.keys(obj);
    let key = null,
      temp = null,
      existobj = null;

    existobj = map.get(obj);
    //如果这个对象已经被记录则直接返回
    if (existobj) {
      return existobj;
    }

    result = {}
    map.set(obj, result);

    for (let i = 0, len = keys.length; i < len; i++) {
      key = keys[i];
      temp = obj[key];
      if (temp && typeof temp === 'object') {
        result[key] = dp(temp);
      } else {
        result[key] = temp;
      }
    }
    return result;
  }
  return dp(obj);
}




const obj1 = {
  x: 1
}
// obj1.z = obj1;

const obj2 = {
  x: 2
}

obj1.next = obj2;
obj2.next = obj1;


const obj3 = deepCopy(obj1);

console.log(obj3)

image.png

二、深拷贝-循环引用处理 weakMap 写法二

//使用Map函数
function deepCopy(obj,map = new Map()){
    if (typeof obj != 'object') return 
    var newObj = Array.isArray(obj)?[]:{}
    if(map.get(obj)){ 
      return map.get(obj); 
    } 
    map.set(obj,newObj);
    for(var key in obj){
        if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] == 'object') {
                newObj[key] = deepCopy(obj[key],map);
            } else {
                newObj[key] = obj[key];
            }
        }
    }
    return newObj;
}
const obj1 = {
        x:1,
        y:2,
        d:{
            a:3,
             b:4
         }
    }
  obj1.z = obj1;
  const obj2 = deepCopy(obj1);
  console.log(obj2)
            
//node 输出{ x: 1, y: 2, d: { a: 3, b: 4 }, z: [Circular] }
//控制台输出{x: 1, y: 2, d: {…}, z: {…}}

三 递归-有循环引用的问题

以下方法如果对象中存在循环引用的情况无法正确处理

function deepCopy(obj) {
  // 创建一个新对象
  let result = {}
  let keys = Object.keys(obj),
    key = null,
    temp = null;

  for (let i = 0; i < keys.length; i++) {
    key = keys[i];  
    temp = obj[key];
    // 如果字段的值也是一个对象则递归操作
    if (temp && typeof temp === 'object') {
      result[key] = deepCopy(temp);
    } else {
    // 否则直接赋值给新对象
      result[key] = temp;
    }
  }
  return result;
}

const obj1 = {
  x: {
    m: 1
  },
  y: undefined,
  z: function add(z1, z2) {
    return z1 + z2
  },
  a: Symbol("foo")
};

const obj2 = deepCopy1(obj1);
obj2.x.m = 2;

console.log(obj1); //{x: {m: 1}, y: undefined, z: ƒ, a: Symbol(foo)}
console.log(obj2); //{x: {m: 2}, y: undefined, z: ƒ, a: Symbol(foo)}

四 递归-解决了循环引用

1) 父级引用

这里的父级引用指的是,当对象的某个属性,正是这个对象本身,此时我们如果进行深拷贝,可能会在子元素->父对象->子元素...这个循环中一直进行,导致栈溢出。比如下面这个例子:

 const obj1 = {
  x: 1, 
  y: 2
};
obj1.z = obj1;

const obj2 = deepCopy1(obj1); \\栈溢出

解决办法是:只需要判断一个对象的字段是否引用了这个对象或这个对象的任意父级即可,可以修改上面的deepCopy函数:


function deepCopy2(obj, parent=null) {
  //创建一个新对象
  let result = {};
  let keys = Object.keys(obj),
     key = null,
     temp = null,
     _parent = parent;
  //该字段有父级则需要追溯该字段的父级
  while(_parent) {
    //如果该字段引用了它的父级,则为循环引用
    if(_parent.originParent === obj) {
      //循环引用返回同级的新对象
      return _parent.currentParent;
    }
    _parent = _parent.parent
  }
  for(let i=0,len=keys.length;i<len;i++) {
    key = keys[i]
    temp = obj[key]
    // 如果字段的值也是一个新对象
    if(temp && typeof temp === 'object') {
      result[key] = deepCopy2(temp, {
        //递归执行深拷贝,将同级的待拷贝对象与新对象传递给parent,方便追溯循环引用
        originParent: obj,
        currentParent: result,
        parent: parent
      });
    } else {
      result[key] = temp;
    }
  }
  return result;
}

const obj1 = {
  x:1
}
obj1.z = obj1;

const obj2 = deepCopy2(obj1);

2) 同级引用

待补充测试用例:



解决方案:父级的引用是一种引用,非父级的引用也是一种引用,那么只要记录下对象A中的所有对象,并与新创建的对象一一对应即可。

参考

总结