浅拷贝与深拷贝

136 阅读4分钟

一、了解概念:

对象拷贝,简而言之就是将对象再复制一份,但是,复制的方法不同将会得到不同的结果。比如直接定义一个新变量赋值为一个对象:

  let obj = {
    name: "张三",
    like: "sing",
  };
  // 2. 直接将对象赋值给变量 newObj
  let newObj = obj;
  // 3.修改obj的like属性
  obj.like = "eat";
  // 4.输出 newObj 
  console.log(newObj);

控制台输出

image.png

小结:我们可以看到,我修改的是obj对象的值,但是newObj里面的值也修改了,这就是浅拷贝,说专业点就是,当创建 obj 对象时,它在堆内存中开辟了一块空间存储对象的内容,而当 newobj 直接赋值为 obj 时,newobj 并不会再重新开辟一块堆内存,而是和obj共用一个地址,这个地址存在栈内存中,所以它俩谁进行修改,都会一起进行改变,请看下图:

image.png

然而这并不是我们想看到的,我们需要的是修改一个,另外一个不受影响,那么如何实现呢?

二、常用方法:

(一)展开运算符:展开运算符使用的对象如果只是针对简单的一级基础数据,就是深拷贝; 展开运算符使用的对象内容包含二级或更多的复杂的数据,那就是浅拷贝,请看例子

1、一级基础数据(没有对象包数组等情况)
let obj = { 
    name: "张三",
    like: "sing", 
    };
    let obj2 = { ...obj };
    obj2.name = '李四';
    console.log(obj.name);

控制台输出:

image.png

小结:通过运算符的方法,修改obj2里的属性值,并没有影响到obj本身,所以算是深拷贝成功了,当然这只是没有复杂嵌套的关系。

2、二级或更多的数据
    name: "张三",
    like: "sing", 
    arr:[1,2,3]
    };
    let obj1 = {...obj}
    // 向数组里添加一个元素
    obj.arr.push(4)
    console.log(obj,obj1);

控制台输出:

image.png

小结:当有二级数据时,展开运算符的方法就不那么管用了,当然这里也可以继续使用展开运算符,不过嵌套的数据越复杂时,可能有些得不偿失,所以碰到此类情况,不做考虑。

(二)Object.assign()方法,主要用于对象合并,将源对象中的属性复制到目标对象中。

let obj = { name: '张三' }; 
    let obj1 = Object.assign({}, obj); 
    obj1.name = '狂徒'; 
    console.log(obj,obj1); 

控制台输出

image.png

小结:可以看到通过Object.assign()方法也可以完成深拷贝。

(三)数组api——concat()slice()方法,常用于拷贝数组。

1、数组.concat()

    let arr = [1, 2];
    let arr1 = arr.concat();
    arr.push(3);
    console.log('这是arr',arr); 
    console.log('这是arr1',arr1) 

控制台输出

image.png

2、数组.slice()

    let arr = [1, 2];
    let arr1 = arr.slice();
    arr.push(3);
    console.log('这是arr',arr); 
    console.log('这是arr1',arr1) 

控制台输出

image.png

小结:可以看到两种数组api实现的方式都是深拷贝。

---------------------------------------------- 分割线 --------------------------------------------------

看到这里,相信有小伙伴会问了,如果只拷贝一层的话,这些方法其实都可以完成深拷贝,但是数据如果出现多层嵌套呢?我们还能实现深拷贝吗?如何进行深拷贝?

说一下深拷贝,它不会像浅拷贝那样只拷贝一层,而是有多少层我就拷贝多少层,要真正的做到全部内容都放在自己新开辟的内存里。可以利用递归思想实现深拷贝,看例子。

(1)使用JSON.parse(JSON.stringify())完成深拷贝

   let obj = { name: '张三', hobby: ['打豆豆'] };
   let obj1 = JSON.parse(JSON.stringify(obj));
   obj.hobby.push('吃饭');
   console.log('这是obj',obj,'这是obj1',obj1); 

控制台输出:

image.png

小结:JSON.stringify将对象先转成字符串,再通过JSON.parse将字符串转成对象,此时对象中每个层级的堆内存都是新开辟的。

(2)使用递归

// 1.定义一个嵌套关系的对象
    let obj = {
      name:'张三',
      age:18,
      hobby:{
        a:'吃饭',
        b:'睡觉',
        c:'打豆豆'
      }
    }
    // 2.封装函数
   function newObj(obj) {
      let Obj1 = {}
      // 3.使用for in 遍历
      for(let key in obj){
        // 判断有无二级关系
        if(typeof obj[key] == "object" && obj[key] != 'null'){
          // 4.使用递归
          Obj1[key] = newObj(obj[key])
        } else {
          // 这是是没有嵌套的情况下
          Obj1[key] = obj[key]
        }
      }
      return Obj1
   }
   // 5.调函数得到新对象
   let Obj1 = newObj(obj)
   // 6.修改值,控制台看变化
   Obj1.hobby.a = '敲代码'
   console.log('这是obj',obj,'这是Obj1',Obj1);

控制台输出:

image.png

小结:拷贝成功,可以看到hobby里a的值发生变化,而原对象并没有改变。

三、总结

浅拷贝能用则用,不能用就使用深拷贝!~