• 面试之JS深拷贝的实现


    在面试中你是否遇到过如下场景:

    Q:小朋友,你是否了解如何拷贝一个对象?

    R:此时,机智的你可能会想到

    Object.assign({}, obj);
    

    Q:那如何深拷贝一个对象呢?

    R:机智的你

    JSON.parse(JSON.stringify(obj));
    

    Q:使用stringify这种方式有何弊端?

    1. 性能问题,stringify再解析其实需要耗费较多时间,特别是数据量大的时候。

    2. 一些类型无法拷贝,例如函数(不输出),正则(输出空对象),时间对象(输出时间字符串),Undefiend(不输出)

    3. 遇到循环引用的对象会出错

    4. 同层(非同层)同引用的问题,理论下两个key对应的val如果指向同一个对象,拷贝也应该指向一个相同新地址才对

    Q:那你能自己实现个深拷贝函数?

    R:如下:

    const deepClone = (obj) => {
      // 非引用类型及函数将直接返回
      if (!obj || typeof obj !== 'object') return obj;
    
      // 特殊的引用类型处理
      switch(Object.prototype.toString.call(obj).slice(8, -1)) {
        case 'Date': 
          return new Date(obj);
          break;
        case 'RegExp': 
          return new RegExp(obj);
          break;
        case 'String': 
          return new String(obj);
          break;
        case 'Number': 
          return new Number(obj);
          break;
        case 'Boolean': 
          return new Boolean(obj);
          break;
      }
    
      const result = obj instanceof Array ? [] : {};
    
      for (let propName in obj) {
        if (obj.hasOwnProperty(propName)) {
          result[propName] = deepClone(obj[propName]);
        }
      }
    
      return result;
    }
    

    优点:实现了大多数数据类型的拷贝,所有非引用类型及引用类型的String Number Boolean Function Array Date RegExp

    缺点:未考虑一些特殊的引用类型如Error Math Symbol Map Set JSON,函数属于引用拷贝,未解决循环引用的问题

    Q:如何解决循环引用?

    R:将父层级的数据缓存对比(可以顺带解决同层(非同层)同引用的问题)

    const deepClone = (obj) => {
      // 非引用类型及函数将直接返回
      if (!obj || typeof obj !== 'object') return obj;
    
      // 特殊的引用类型处理
      switch (Object.prototype.toString.call(obj).slice(8, -1)) {
        case 'Date':
          return new Date(obj);
          break;
        case 'RegExp':
          return new RegExp(obj);
          break;
        case 'String':
          return new String(obj);
          break;
        case 'Number':
          return new Number(obj);
          break;
        case 'Boolean':
          return new Boolean(obj);
          break;
      }
    
      const map = deepClone.map = deepClone.map || new Map();
    
      // 使用map结构可以不必循环缓存,提高效率
      if (map.get(obj)) {
        return map.get(obj);
      }
    
      const result = obj instanceof Array ? [] : {};
    
      // 如果仔细观察可以发现解决了同层同引用的问题
      map.set(obj, result);
    
      for (let propName in obj) {
        if (obj.hasOwnProperty(propName)) {
          result[propName] = deepClone(obj[propName]);
        }
      }
    
      return result;
    }
    

    Q:为什么函数还是指向原来的函数,而不创建新函数?

    R:理论下函数也可以通过new Function(code)来创建新的函数,但是如果遇到闭包函数,我们无法得到原函数的外层定义的变量及其原有作用域链,这些在JS词法解析时完成的步骤我们无法得知,所有只能引用原函数比较好。

    Sum: 上面实现的缺点主要是没有完全覆盖特殊引用类型,但其实我们平时应该不会遇到那些类型,所以可以凑合使用。如果还有其它的问题没有考虑到或者有出错的,希望大家可以帮忙指出。

    参考


    欢迎到前端学习打卡群一起学习~516913974

  • 相关阅读:
    学习python第四天——列表方法
    昨天休息了一天,今天补上。列表学习
    第二天学习——字符串的方法
    mongodb
    linux学习篇(一)
    mysql优化
    linux服务
    vue配置服务器
    photoshop学习
    关于视图
  • 原文地址:https://www.cnblogs.com/formercoding/p/12839953.html
Copyright © 2020-2023  润新知