• 【JavaScript】面试手撕深拷贝



    鑫宝Code

    🌈个人主页: 鑫宝Code
    🔥热门专栏: 闲话杂谈炫酷HTML | JavaScript基础
    💫个人格言: "如无必要,勿增实体"


    引入

    上次讲了浅拷贝,这次我们来讲深拷贝。有一说一,深拷贝也算是面试时非常常见的题目了。🐶

    深拷贝的作用

    首先为什么需要深拷贝,因为浅拷贝无法满足我们对原始数据完整、独立复制的需求。我们希望修改新对象不会影响原对象。

    深浅拷贝的区别

    这里引用ConardLi大佬的理解

    浅拷贝

    创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。

    • 如果属性是基本类型,拷贝的就是基本类型的值.
    • 如果属性是引用类型,拷贝的就是内存地址

    所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

    深拷贝

    将一个对象从内存中完整的拷贝一份出来

    • 从堆内存中开辟一个新的区域存放新对象
    • 且修改新对象不会影响原对象

    深拷贝实现方式

    JSON.parse(JSON.stringify())

    遥记当年,我当时还是大三的时候,背了一周的面经就跑去字节面试实习生了。面试官就让我手撕深拷贝。

    我当时才20刚出头,前端面经也才抱起来背了不到一周。这种题目我写的来得?

    跟面试官面面相觑了半天,突然灵机一动,JSON.parse(JSON.stringfy())大法一定可以。

    我当时非常开心的说出了这个答案, 面试官当时好像有点尬住了,嘴角流露出一股察摸不到的笑容。

    但可能由于接受过专业的训练,也只在那短短的时间内便消失不见。🐶

    介绍

    JSON.parse(JSON.stringify()),首先使用利用JSON.stringify将对象转成JSON字符串。 再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

    使用例子
    const a = {
        name: '张三',
        score:{
            math: 100
        }
    }
    
    const b = JSON.parse(JSON.stringify(a));
    
    // 改变b中的对象的值
    b.score.math = 60;
    
    console.log('a的值',a);
    console.log('b的值',b);
    
    /**
     * 输出的结果如下
     * a的值 { name: '张三', score: { math: 100 } }
     * b的值 { name: '张三', score: { math: 60 } }
     */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    缺点

    记个TODO:下次写文章详细分析下JSON.stringify的缺点。

    • 不会拷贝对象上为undefined的值
    • 不能处理函数
    • 不能处理正则
    • 循环引用会报错
    • Symol会丢失等

    Lodash的cloneDeep

    续借上文,面试官笑了笑,说JSON.parse(JSON.stringify())这个方式有如上几个缺点,你能不能换个更好的方式将这个问题解决呢?

    这又一次的让我陷入了思索,又开始了与面试官的面面相觑😅。突然我想起了以前用的Lodash,其中有一个NB的方法。cloneDeep,当时我洋洋得意,心想lodash库的方法,总不可能还有缺点吧?

    此时,面试官的表情稍稍有点微妙,我的第六感告诉我,我好像答错了,不过我认为我回答的没问题呀。

    晌久,面试官叹了口气说,我是让你手撕,手撕懂吗?

    介绍

    _.cloneDeeplodash库提供的深拷贝的方法,非常实用,建议背诵😂。

    使用例子
    import * as _ from "lodash";
    
    const a = {
      name: "张三",
      score: {
        math: 100,
      },
    };
    
    const b = _.cloneDeep(a);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    缺点

    暂无,🐮的lodash库,🐮的cloneDeep函数。

    手撕深拷贝

    诶,还是得手撕呀,来吧来吧,还是得给面试官露一手的😜

    基础版本

    多年面试经验告诉我,一般写出这个版本,几乎都让过了,顶多在回答一下循环引用问题如何解决。一般不太会让写一个比较完美的深拷贝的。🐶

    1. 首先,我们要拷贝的数据类型有两种,分别是ArrayObject
    2. 如果对象里的属性还是对象,那么采用递归对这个对象再进行拷贝
    3. 如果对象里的属性不是对象,那么直接返回即可。

    代码如下:

    function deepClone(target) {
      if (typeof target === "object") {
        let cloneTarget = Array.isArray(target) ? [] : {};
        for (const key in target) {
          cloneTarget[key] = deepClone(target[key]);
        }
        return cloneTarget;
      } else {
        return target;
      }
    }
    
    const a = {
      name: "张三",
      score: {
        math: 100,
      },
    };
    
    const b = deepClone(a);
    /** 输出的b与a一样 **/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    但是这样实现会有若干个问题:

    1. 循环引用问题无法解决
    2. DateRegExp等对象无法拷贝

    进阶版本

    之所以用weakMap,是因为weakMap的键是弱引用,可以在任何时刻被回收。

    如果想了解更清楚进阶深拷贝的原理,可以参阅 如何写出一个惊艳面试官的深拷贝?

    function deepClone(target, hash = new WeakMap()) {
      if (target === null) return null;
      if (typeof target !== "object") return target;
      if (target instanceof Date) return new Date(target);
      if (target instanceof RegExp) return new RegExp(target);
      // 如果hash里有值,立马返回
      if (hash.has(target)) return hash.get(target);
    
      const cloneTarget = Array.isArray(target) ? [] : {};
    
      hash.set(target,cloneTarget);
    
      if (typeof target === "object") {
        for (const key in target) {
          cloneTarget[key] = deepClone(target[key],hash);
        }
        return cloneTarget;
      } else {
        return target;
      }
    }
    
    const a = {
      name: "张三",
      score: {
        math: 100,
      },
      date: new Date(),
      regex: /^\d{3,4}-\d{5,8}$/,
    };
    
    a.child = a;
    
    const b = deepClone(a);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    运行结果如下:

    参考资料

    如何写出一个惊艳面试官的深拷贝?

    浅拷贝与深拷贝

  • 相关阅读:
    基于Fomantic UI Web构建 个人导航站点网站源码 网站技术导航源码
    树莓派也能用于心脏病数据安全管理!
    【Image captioning】Meshed-memory transformer在自定义数据集的训练与调试
    LabVIEW使用源代码控制
    Cortex-M3/M4之SVC和PendSV异常
    一文带你快速搭建框架(最全MyBatis笔记)
    玩具机器人脚本适合场景
    FCPX教程|如何在Final Cut Pro 的时间线中调整转场?
    新闻软文稿件媒体发布怎么做?纯干货
    ShuffleNetV2:设计轻量化卷积神经网络的理论准则和应用实现
  • 原文地址:https://blog.csdn.net/qq_44214428/article/details/136637785