深拷贝浅拷贝区别以及实现深拷贝的几种方法

1,309 阅读4分钟

如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,如果B没变,那就是深拷贝

我们来举个浅拷贝例子:

      let a = [0, 1, 2, 3, 4],
        b = a;
      console.log(a === b);
      a[0] = 1;
      console.log(a, b);

image.png

嗯?明明b复制了a,为啥修改数组a,数组b也跟着变了?

image.png

那么这里,就得引入基本数据类型与引用数据类型的概念了。

基本数据类型,numberstringbooleannullundefinedsymbol以及未来ES10新增的BigInt(任意精度整数)七类。
引用数据类型(Object类)有常规名值对的无序对象{a:1},数组[1,2,3],以及函数等。

而这两类的数据存储模式是这样的。

基本数据类型

名值存储在栈内存中,例如let a=1;

image.png

当执行b=a后,栈内存会新开辟一个内存,例如这样:
image.png

这一看就是两个独立的嘛,各管各的对不对,所以一个改变影不影响另一个。而引用类型就不一样了,来看:

引用类型

引用数据类型–名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值,我们举个例子画个图:
当我们let a=[1,2,3,4]的时候,会:
image.png

当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。
image.png

而当我们a[0]=6时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。
image.png
那么如果我们的b指向的是一个独立的堆内存呢?只是val一样而已。就像下图所示,岂不就达到深拷贝的效果了
image.png

说了那么多,来谈谈实现的方法吧!

方法一

JSON.parse(JSON.stringify)

      const obj = {
        num: 3,
        str: "啦啦啦",
        boy: {
          height: 21,
          weight: 105,
        },
        arr: [1, 2, 3],
        fn: function () {
          console.log(2333);
        },
        reg: /"/g,
        date: new Date(),
      };
      const deep = JSON.parse(JSON.stringify(obj));
      deep.boy.height = 30;
      console.log(obj);
      console.log(deep); //fn 没了 reg 为对象 date为字符串了

image.png

我们发现DateRegExp类型的变成了{},而我的函数而是直接没有了。

方法二

通过递归实现,判断是不是属性object类型,如果是则递归clone这个属性值,如果是基本类型了,则直接赋值

      const obj = {
        num: 3,
        str: "啦啦啦",
        boy: {
          height: 21,
          weight: 105,
        },
        arr: [1, 2, 3],
        fn: function () {
          console.log(2333);
        },
        reg: /"/g,
        date: new Date(),
      };

      function deepClone(obj) {
        let objClone = Array.isArray(obj) 1 [] : {};
        //如果是对象,则递归对其属性进行深拷贝
        if (obj && typeof obj === "object") {
          //取出键
          for (let key in obj) {
            //如果是对象并且是这个对象的属性
            if (obj[key] && typeof obj[key] === "object") {
                   newObj[i] =
                Object.prototype.toString.call(oldObj[i]) === "[object Array]"
                  ? []
                  : {};
              deepClone(newObj[i], oldObj[i]);
            } else {
              //如果不是,简单复制
              objClone[key] = obj[key];
            }
          }
        }
        return objClone;
      }

      const obj2 = deepClone(obj);
      console.log(obj2);

image.png

从上图看出,虽然函数有了,但是RegExpDate类型还是一个结果,那么我们得为它们俩重新开辟一个相同类型的空间了!

方法三

循环递归,判断类型声明对应的类型进行拷贝。

      const obj = {
        num: 3,
        str: "啦啦啦",
        boy: {
          height: 21,
          weight: 105,
        },
        arr: [1, 2, 3],
        fn: function () {
          console.log(2333);
        },
        reg: /"/g,
        date: new Date(),
      };
      const obj2 = {};
      deepClone(obj2, obj);
      obj.arr.push("4");
      console.log(obj);
      console.log(obj2);
      obj2.fn(); //2333

      //为了解决刚才的reg  date拷贝后为对象的问题
      function deepClone(newObj, oldObj) {
        //遍历老对象
        if (oldObj) {
          for (let i in oldObj) {
            if (oldObj[i] instanceof Date) {
              newObj[i] = new Date(oldObj[i]);
            } else if (oldObj[i] instanceof RegExp) {
              newObj[i] = new RegExp(oldObj[i]);
            } else if (typeof oldObj[i] === "object") {
                   newObj[i] =
                Object.prototype.toString.call(oldObj[i]) === "[object Array]"
                  ? []
                  : {};
              deepClone(newObj[i], oldObj[i]);
            } else {
              newObj[i] = oldObj[i];
            }
          }
        }
      }

运行结果如下:
image.png

这下就ok啦!

补充

有一些方法可以实现第一层的浅层复制

slice()||slice(0)

      const array = [1, 2, 3, 4, { a: 5 }];
      const obj2 = array.slice();
      array[0] = "aaa";
      array[4].a = 7;
      console.log(array, obj2);

image.png

途中我们可以看到,当修改第一层的时候,obj2是没有发生变化了,当修改第二层或者更深层的时候就发生变化拉!

扩展运算符…

      const array = [1, 2, 3, 4, { a: 5 }];
      const newArray = [...array];

      array[0] = "aaa";
      array[4].a = "aaaa";
      console.log(array, newArray);

和上图一样啦。