• 【前端小tip】深拷贝不能处理函数的解决方法,文末包含所有深拷贝常见问题的解决方法


    在开发过程中,我对对象进行深拷贝的时候常常使用序列化和反序列化,也就是

    const newObj = JSON.parse(JSON.stringify(obj))
    
    • 1

    这个方法很好用,但是在最近我发现了一个弊端,就是它只能处理只含有基础类型属性和对象属性的对象,对函数没办法处理,例如以下对象

    const obj = {
      name: "obj",
      friend: {
        name: "saber"
      },
      foo() {
        console.log("foo~")
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在用序列化和反序列化进行深拷贝后会输出:

    {
      name: 'obj',
      friend: { name: 'saber' },
      foo: [Function: foo]
    }	
    
    • 1
    • 2
    • 3
    • 4
    • 5

    很明显可以看出对函数的处理不是我们想要的结果,于是我们需要考虑用递归➕对象特判的方法实现

    function deepClone(target) { 
        //检测数据类型
        if (typeof target !== 'object' || target === null) {
            // target 是 null ,或者不是对象和数组,说明是原始类型,直接返回
            return target;
        } else { 
            //创建一个容器,存储数组或者对象
            const result = Array.isArray(target) ? [] : {};
            //遍历target
            for(let key in target) { 
                //检测该属性是否为对象本身的属性(不能拷贝原型对象的属性)
                if(target.hasOwnProperty(key)) { 
                	//递归遍历子元素,直到能返回原始值
                    result[key] = deepClone2(target[key]);
                }
            }
            return result;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    这样就可以成功处理函数了,输出如下:

    {
      name: 'obj',
      friend: {
        name: 'saber'
      },
      foo: {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在网上冲浪看看其他大神的解决办法,发现其他大神还发现了诸如循环引用引起无限递归、日期和正则表达式函数无法处理、Map和Set不好处理等问题,这里引用一个大神的方法,他几乎解决了所有常见问题,我也放进文章作为笔记:

       function deepClone(target) {
            // 创建一个 WeakMap 来保存已经拷贝过的对象,以防止循环引用
            const map = new Map();
    
            // 辅助函数:判断一个值是否为对象或函数
            function isObject(target) {
              return (
                (typeof target === "object" && target) || // 检查是否是非null的对象
                typeof target === "function" // 或者是函数
              );
            }
    
            // 主要的拷贝函数
            function clone(data) {
              // 基本类型直接返回
              if (!isObject(data)) {
                return data;
              }
    
              // 对于日期和正则对象,直接使用它们的构造函数创建新的实例
              if ([Date, RegExp].includes(data.constructor)) {
                return new data.constructor(data);
              }
    
              // 对于函数,创建一个新函数并返回
              if (typeof data === "function") {
                return new Function("return " + data.toString())();
              }
    
              // 检查该对象是否已被拷贝过
              const exist = map.get(data);
              if (exist) {
                return exist; // 如果已经拷贝过,直接返回之前的拷贝结果
              }
    
              // 如果数据是 Map 类型
              if (data instanceof Map) {
                const result = new Map();
                map.set(data, result); // 记录当前对象到 map
                data.forEach((val, key) => {
                  // 对 Map 的每一个值进行深拷贝
                  result.set(key, clone(val));
                });
                return result; // 返回新的 Map
              }
    
              // 如果数据是 Set 类型
              if (data instanceof Set) {
                const result = new Set();
                map.set(data, result); // 记录当前对象到 map
                data.forEach((val) => {
                  // 对 Set 的每一个值进行深拷贝
                  result.add(clone(val));
                });
                return result; // 返回新的 Set
              }
    
              // 获取对象的所有属性,包括 Symbol 类型和不可枚举的属性
              const keys = Reflect.ownKeys(data);
              // 获取对象所有属性的描述符
              const allDesc = Object.getOwnPropertyDescriptors(data);
              // 创建新的对象并继承原对象的原型链
              const result = Object.create(Object.getPrototypeOf(data), allDesc);
    
              map.set(data, result); // 记录当前对象到 map
    
              // 对象属性的深拷贝
              keys.forEach((key) => {
                result[key] = clone(data[key]);
              });
    
              return result; // 返回新的对象
            }
    
            return clone(target); // 开始深拷贝
          }
    
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    // 测试的sample对象
    const sample = {
        // =========== 1.基础数据类型 ===========
        numberVal: 123,
        stringVal: "OpenAI",
        booleanVal: false,
        undef: undefined,
        nil: null,
        symKey: Symbol("key"),
        bigNumber: BigInt(1234567890n),
        // =========== 2.Object类型 ===========
        // 普通对象
        user: {
            firstName: "John",
            lastName: "Doe",
        },
        // 数组
        list: ["apple", "banana", "cherry"],
        // 函数
        display: function() {
            console.log("This is a display function");
        },
        // 日期
        birthDate: new Date(2000, 0, 1),
        // 正则
        pattern: new RegExp("/pattern/g"),
        // Map
        translations: new Map().set("hello", "hola"),
        // Set
        tags: new Set().add("fruit").add("food"),
        // =========== 3.其他 ===========
        [Symbol("unique")]: "uniqueValue",
    };
    
    // 4.添加不可枚举属性
    Object.defineProperty(sample, "hidden", {
        enumerable: false,
        value: "Hidden Property",
    });
    
    // 5.设置原型对象
    Object.setPrototypeOf(sample, {
        prototypeKey: "prototypeValue",
    });
    
    // 6.设置loop成循环引用的属性
    sample.self = sample;
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    输出结果:
    在这里插入图片描述

    出处点这里
    探究JavaScript中的深拷贝:细节与实现

  • 相关阅读:
    Authing 获得 ISO/IEC 20000-1 服务管理体系和信息安全管理体系认证
    consul introduction
    电脑磁盘怎么加密?磁盘加密软件哪个更好用?
    RT-Thread 原子操作(学习)
    nodejs基于vue 学籍管理系统
    Direct3D网格(一)
    【R语言】概率密度图
    轻量级业务福音!TDengine Cloud 在国轩高科储能项目中的应用
    mac Network: use --host to expose
    蓝眼开源云盘部署全过程(手动安装)
  • 原文地址:https://blog.csdn.net/fangyuan__/article/details/132790063