对象深复制的方法之完整版(带注释)

声明:本方法几乎考虑到了所有类型的对象,比如函数、日期、正则、Set、Map、DOM元素等,如有不足敬请指出。

首先写好一个包含各种类型数据的对象,如下:

      var d = Symbol();
      var e = Symbol();
      var ss = { a: 1 };
      var obj = {
        a: 1,
        b: 2,
        c: [1, 2, 3],
        zz: new Set([1, 2, ss]),
        yy: new Map(),
        [d]: "aaa",
        z: document.createElement("div"),
        d: {
          e: new Date(),
          f: /a/g,
          g: function (s) {
            console.log(s);
          },
          h: {},
        },
      };
      Object.defineProperties(obj.d.h, {
        i: {
          value: 10,
        },
        j: {
          configurable: true,
          writable: true,
          value: [1, 2, 3, 4],
        },
        k: {
          writable: true,
          value: {
            l: {},
            m: "abcde",
            n: true,
            o: [1, 2, 3],
          },
        },
        [e]: {
          value: ["a", "b", "c", "e"],
        },
      });

      Object.defineProperties(obj.d.h.k.l, {
        p: {
          value: function () {
            console.log("p");
          },
        },
        q: {
          value: {
            r: { a: 1 },
            j: { b: 2 },
          },
        },
      });
      var a_1 = { a: 1 };
      var a_2 = { b: 2 };
      obj.yy.set("name", "xili");
      obj.yy.set(a_1, a_2);

接下来是将对象进行深复制的方法cloneObject,参数中的source指的是源对象(被复制的对象),target指的是目标对象(复制后得到的新对象)。

      function cloneObject(source, target) {
        if (source === null || source === undefined) return source;
        if (source === document) return;

        // 如果target不是引用类型,可能是undefined、null、bool、number、string或者为空
        if (!Object.prototype.isPrototypeOf(target)) {
          // 根据源对象创建一个相同类型的对象赋值给target

          if (HTMLElement.prototype.isPrototypeOf(source)) {
            // 如果源对象是DOM元素
            // 就根据源对象的节点名创建一个新的DOM元素赋值给target
            target = document.createElement(source.nodeName);
          } else if (source.constructor === RegExp) {
            // 如果源对象是正则表达式
            // 则将源对象的source属性和flags属性作为参数通过构造函数创建新的正则表达式并赋值给target
            target = new RegExp(source.source, source.flags); // 任何正则表达式都有source和flags属性,source是正则内容,flags是正则修饰符,而这两个属性都是只读属性,不能写入,必须通过构造函数创建时带入
          } else if (source.constructor === Date) {
            // 如果源对象是日期对象
            // 将原有的日期对象放入新创建的日期对象,可以让当前日期对象变为原有日期对象的值,但是没有引用关系
            target = new Date(source);
          } else if (source.constructor === Function) {
            // 如果源对象是函数
            // 将源函数的参数和函数体内容提取到数组中,然后通过new Function()创建新的函数
            var arr = source
              .toString()
              .replace(/\n|\r/g, "")
              .trim()
              .match(/\((.*?)\)\s*\{(.*)\}/)
              .slice(1);
            target = new Function(arr[0].trim(), arr[1]);
          } else if (source.constructor === Set) {
            // 如果源对象是Set类型
            // 因为new Set时可以传入数组,所以我们将原有的Set列表强转为数组,并且将这个强转后的数组深复制以后代入到新的Set中
            target = new Set(cloneObject(Array.from(source.values())));
          } else if (source.constructor === Map) {
            // 如果源对象是Map类型
            // 先创建一个新的Map赋值给target
            target = new Map();
            // 再遍历原来的Map
            for (var [key, value] of source.entries()) {
              // 如果key是引用类型
              if (Object.prototype.isPrototypeOf(key)) {
                // 如果value是引用类型
                if (Object.prototype.isPrototypeOf(value)) {
                  // 则将key和value分别做深复制,并将返回的结果放在target中
                  target.set(cloneObject(key), cloneObject(value));
                } else {
                  // 如果value不是引用类型,则只将key深复制,并且将返回的结果放在target中
                  target.set(cloneObject(key), value);
                }
              } else {
                if (Object.prototype.isPrototypeOf(value)) {
                  target.set(key, cloneObject(value));
                } else {
                  target.set(key, value);
                }
              }
            }
          } else {
            // 否则源对象就只是单纯的对象
            // 则直接通过对象类型的反射创建新的同类型对象赋值给target
            target = new source.constructor();
          }
        }
        // 获取源对象的所有字符属性名和Symbol属性名,并保存到一个数组中
        var names = Object.getOwnPropertyNames(source).concat(
          Object.getOwnPropertySymbols(source)
        );

        // 遍历源对象的所有属性名
        for (var i = 0; i < names.length; i++) {
          // 如果当前复制的是函数,并且遍历到的当前属性名是prototype,则这个属性不复制,否则会死循环
          if (source.constructor === Function && names[i] === "prototype")
            continue;
          // 获取当前属性名的描述对象
          var desc = Object.getOwnPropertyDescriptor(source, names[i]);

          // 如果当前描述对象的值是引用类型,则将源对象的描述内容设置给当前目标对象相同属性名的描述内容,并且将源对象的值进行深复制后设置给当前目标对象的值
          if (Object.prototype.isPrototypeOf(desc.value)) {
            Object.defineProperty(target, names[i], {
              configurable: desc.configurable,
              enumerable: desc.enumerable,
              writable: desc.writable,
              value: cloneObject(desc.value),
            });
          } else {
            // 否则直接将描述对象设置给目标对象的这个属性
            Object.defineProperty(target, names[i], desc);
          }
        }

        return target;
      }

下面简单证明一下目标对象和源对象完全没有引用关系(包括里面所包含的所有引用类型数据)。

      var o = cloneObject(obj);
      a_1.a = 100;
      obj.d.h.j[1] = 1000;
      console.log(o, obj);

大家可以实际打印一下,看看打印结果是否相同。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值