[前端面试]深拷贝分析

176 阅读5分钟
date: 2022-08-27 18:00:00

深浅拷贝介绍

浅拷贝 : 复制目标对象所有属性的引用指针, 修改复制的一维数据不影响原对象, 修改二维及以上的数据会影响原始对象.

深拷贝 : 完整拷贝对象到另一片内存中, 修改复制的数据不影响原对象.

可能概念上的区别看的有点懵, 看点代码

let person, human;
person = {
  name: 'lisnote',
  body: {
    height: 170,
  },
};
console.log('原对象输出:', person);
// 原对象输出: { name: 'lisnote', body: { height: 170 } }
// ---------------浅拷贝--------------------
human = { ...person };
human.name = '不知名名菜';
human.body.height = 175;
console.log('浅拷贝输出:', person);
// 浅拷贝输出: { name: 'lisnote', body: { height: 175 } }
// ---------------深拷贝--------------------
human = JSON.parse(JSON.stringify(person));
human.name = '匿名知名菜';
human.body.height = 180;
console.log('深拷贝输出:', person);
// 拷贝输出: { name: 'lisnote', body: { height: 175 } }

对比三次输出

输出类型输出person与上次输出变化
初次输出{ name: 'lisnote', body: { height: 170 } }
修改浅拷贝后输出{ name: 'lisnote', body: { height: 175 } }name无变化 body.height变化
修改深拷贝后输出{ name: 'lisnote', body: { height: 175 } }无变化

可以看到

  1. 修改浅拷贝对象的一维数据, 原始对象不受影响, 修改浅拷贝的高维数据则原始对象也受影响
  2. 修改深拷贝对象的数据一维和高维数据, 原始对象都不会受到影响

笔试解

function deepClone(obj) {
  let cloneObj = Object.create(Object.getPrototypeOf(obj));
  Object.getOwnPropertyNames(obj).forEach((o) => {
    cloneObj[o] = obj[o] instanceof Object ? deepClone(obj[o]) : obj[o];
  });
  return cloneObj;
}

面试

面试的时候面试官让我对我的题目做一个思路分析,

我当时挺懵的, 我做完才注意到考的不是实现而是算法, 我上面还有题浅拷贝用拓展运算符..., 于是在草草的进行了思路分析之后给自己加了点戏,

"其实我这个克隆还有点问题没有解决, 我可以继续跟你分析"

  1. 遍历问题, getOwnPropertyNames 无法获取到以Symbol为属性的数据, 所以这里是并没有克隆到以Symbol为属性名的值, 若要解决需要使用 getPropertySymbol 再进行一次遍历.

    不过所谓优化无止境, 还有一些很特殊的值目前没有遍历, 比如Vue3的 toRaw 方法是通过读取一个__v_raw 属性来获取被 Proxy 的真实对象的, 而该属性并非是以 defineProperty 或是直接赋值进行定义, 他写在 Proxy 的 get 方法一个 if 条件之中, 只有 Proxy 判断读取属性为 __v_raw 时, 这个属性才起作用, 目前我还不能获取到这样极端属性.

    // 解决Symbol不遍历
    function deepClone(obj) {
      let cloneObj = Object.create(Object.getPrototypeOf(obj));
      [
        ...Object.getOwnPropertyNames(obj),
        ...Object.getOwnPropertySymbols(obj),
      ].forEach((o) => {
        cloneObj[o] = obj[o] instanceof Object ? deepClone(obj[o]) : obj[o];
      });
      return cloneObj;
    }
    
  2. 值特殊类型问题, 笔试解的那个例子适合克隆json数据, 而像是 Set, WeakMap, Function 等类型是有问题的, 甚至有报错的可能, 例如WeakMap 在 instanceOf Object 都是 true 所以会被递归调用, 但是因为不可遍历而会返回一个空WeakMap, 特殊类型太多, 笔试时间有限, 如有需要往细里写我也可以像Vue3一样用 Object.prototype.toString.call(value).slice(8, -1) 获取属性名后再根据不同的类型进行对应的处理

  3. 值属性问题, 这个克隆并没有做到将值原原本本的克隆, 在JavaScript中, 值是带有属性的, 比如数组类型的 length 属性不可遍历, 而这个深克隆并没有做到, 正确的做法应该是使用 getOwnPropertyDescriptor 后再用 defineProperty 定义属性.

    // 更改为适合针对特殊类型处理的结构
    // 添加值时带有属性
    function deepClone(obj) {
      let type = Object.prototype.toString.call(obj).slice(8, -1);
      if (['Object', 'Array'].indexOf(type) > -1) {
        let cloneObj = Object.create(Object.getPrototypeOf(obj));
        [
          ...Object.getOwnPropertyNames(obj),
          ...Object.getOwnPropertySymbols(obj),
        ].forEach((o) => {
          Object.defineProperty(cloneObj, o, {
            ...Object.getOwnPropertyDescriptor(obj, o),
            value: deepClone(obj[o]),
          });
        });
        return cloneObj;
      }
      // if (type === 'Set') {}
      return obj;
    }
    
  4. 循环引用问题, 当对象中存在循环引用的时候, 这个深克隆方法会出现超出最大调用堆栈的错误, 如果要解决可以为需要递归遍历的对象建立一个 WeakMap ,当重复引用的时候就使用 WeakMap 中已经克隆过的值.

    function deepClone(obj, mapper = new WeakMap()) {
      let type = Object.prototype.toString.call(obj).slice(8, -1);
      if (['Object', 'Array'].indexOf(type) > -1) {
        if (mapper.has(obj)) return mapper.get(obj);
        let cloneObj = Object.create(Object.getPrototypeOf(obj));
        mapper.set(obj, cloneObj);
        [
          ...Object.getOwnPropertyNames(obj),
          ...Object.getOwnPropertySymbols(obj),
        ].forEach((o) => {
          Object.defineProperty(cloneObj, o, {
            ...Object.getOwnPropertyDescriptor(obj, o),
            value: deepClone(obj[o], mapper),
          });
        });
        return cloneObj;
      }
      // if (type === 'Set') {}
      return obj;
    }
    

茶余饭后

  • 网上有些写的很好的深克隆案例, 写的很详细, 不过了解原理就好, 笔试别照着写, 因为纸张方寸地, 写不下那么多代码, 面试官也不会给你那么多时间, 毕竟面试不是写工具库, 在尽量少的代码展现出尽量多的思考更为合适
  • 记录2022.7.5-2022.7.9的某次面试, 笔试时有实现深拷贝, 我的答案以及我的分析, 写完之后瞄了一眼试卷顶部, 原来试卷的名字叫做<<前端算法笔试题>>, 心里凉了一半, 我这写法, API多算法少.
  • 面试前我看到他在我给他的简历上写了个B+, 不知道是简历评价还是笔试成绩, 我拍了照片发我哥, 我哥跟我说说不定可有S评价, 我大笑说最高应该是SSR🤣(不会真的有S评价吧?)
  • 原来笔试允许用手机
  • 你面试疯狂强调自己看过Vue源码的样子真狼狈, 但你面试通过的样子真的很靓仔