Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Day241:说一下深拷贝如何解决循环引用问题? #1060

Open
Genzhen opened this issue Feb 18, 2021 · 2 comments
Open

Day241:说一下深拷贝如何解决循环引用问题? #1060

Genzhen opened this issue Feb 18, 2021 · 2 comments

Comments

@Genzhen
Copy link
Collaborator

Genzhen commented Feb 18, 2021

每日一题会在下午四点在交流群集中讨论,五点小程序中更新答案
欢迎大家在下方发表自己的优质见解
二维码加载失败可点击 小程序二维码

扫描下方二维码,收藏关注,及时获取答案以及详细解析,同时可解锁800+道前端面试题。


循环引用问题

看个例子

function deepCopy(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 会报错,栈溢出。

  • obj 对象存在循环引用时,打印它时是不会栈溢出
  • 深拷贝 obj 时,才会导致栈溢出

循环应用问题解决

  • 即:目标对象存在循环应用时报错处理

大家都知道,对象的 key 是不能是对象的。

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

参考解决方式一:使用 weekmap:

解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系

这个存储空间,需要可以存储key-value形式的数据,且key可以是一个引用类型,

我们可以选择 WeakMap  这种数据结构:

  • 检查 WeakMap  中有无克隆过的对象
  • 有,直接返回
  • 没有,将当前对象作为key,克隆对象作为value进行存储
  • 继续克隆
function isObject(obj) {
  return (typeof obj === "object" || typeof obj === "function") && obj !== null;
}
function cloneDeep(source, hash = new WeakMap()) {
  if (!isObject(source)) return source;
  if (hash.has(source)) return hash.get(source); // 新增代码,查哈希表

  var target = Array.isArray(source) ? [] : {};
  hash.set(source, target); // 新增代码,哈希表设值

  for (var key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      if (isObject(source[key])) {
        target[key] = cloneDeep(source[key], hash); // 新增代码,传入哈希表
      } else {
        target[key] = source[key];
      }
    }
  }
  return target;
}

参考解决方式二:

可以用 Set,发现相同的对象直接赋值,也可用 Map

const o = { a: 1, b: 2 };
o.c = o;

function isPrimitive(val) {
  return Object(val) !== val;
}
const set = new Set();
function clone(obj) {
  const copied = {};
  for (const [key, value] of Object.entries(obj)) {
    if (isPrimitive(value)) {
      copied[key] = value;
    } else {
      if (set.has(value)) {
        copied[key] = { ...value };
      } else {
        set.add(value);
        copied[key] = clone(value);
      }
    }
  }
  return copied;
}
@qzruncode
Copy link

function deepCloneObj(obj) {
  if(obj == null) return obj;
  const o = Object.assign({}, obj);
  Object.keys(o).forEach(k => {
    const v = o[k];
    if(Object.prototype.toString.call(v) == '[object Object]') {
      // 如果是循环引用,会造成递归爆栈,使用set集合装对象,如果有相同的对象直接返回
      const set = Object.prototype.toString.call(deepCloneObj[Symbol.for('cache')]) == "[object Set]" 
        ? deepCloneObj[Symbol.for('cache')]
        : deepCloneObj[Symbol.for('cache')] = new Set();
      if(set.has(v)) {
        o[k] = v;
      }else{
        set.add(v);
        o[k] = deepCloneObj(v);
      }
    }else if(Object.prototype.toString.call(v) == '[object Array]') {
      o[k] = deepCloneArr(v);
      function deepCloneArr(arr) {
        const tmp = [];
        for(let i = 0; i < arr.length; i++) {
          const item = arr[i];
          if(Object.prototype.toString.call(item) == '[object Object]') {
            tmp.push(deepCloneObj(item));
          }else if(Object.prototype.toString.call(item) == '[object Array]') {
            tmp.push(deepCloneArr(item));
          }else {
            tmp.push(item)
          }
        }
        return tmp;
      }
    }
  })
  return o;
}
var obj = {
  a: 1,
  b: 2,
  c: [1, 2, 3],
  d: { aa: 1, bb: 2 },
};
obj.e = obj;
deepCloneObj(obj);

@CHENQIKUAI
Copy link

var test1 = { a: { a: 'a' } };
test1.z = test1;

var test2 = clone(test1);
test2.z.z.a.a = 'z';
console.log(test1.a.a); // 会输出 'z',但不是应该输出'a'吗

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants