lgwebdream / FE-Interview

🔥🔥🔥 前端面试,独有前端面试题详解,前端面试刷题必备,1000+前端面试真题,Html、Css、JavaScript、Vue、React、Node、TypeScript、Webpack、算法、网络与安全、浏览器
https://lgwebdream.github.io/FE-Interview/
Other
6.77k stars 896 forks source link

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

Open Genzhen opened 3 years ago

Genzhen commented 3 years ago

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

扫描下方二维码,收藏关注,及时获取答案以及详细解析,同时可解锁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 会报错,栈溢出。

循环应用问题解决

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

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

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

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

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

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

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 commented 3 years ago
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 commented 1 year ago
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'吗