深拷贝和浅拷贝

一、基本类型和引用类型

  • 基本数据类型(值类型):String 字符串、Number 数值、Boolean 布尔值、Null 空值、Undefined 未定义。

  • 引用数据类型(引用类型):Object 对象。

    注意:内置对象 Function、Array、Date、RegExp、Error等都是属于 Object 类型。也就是说,除了基本数据类型之外,其他的,都称之为 Object类型。

二、浅拷贝和深拷贝

如图所示:

  • obj2是对obj1的浅拷贝,obj2新建了一个对象,但是obj2对象复制的是obj1的指针,也就是obj1的堆内存地址,而不是复制对象本身。obj1和obj2是共用了内存地址的。
  • obj3是对obj1的深拷贝,obj3和obj1不共享内存。

概念:

  • 浅拷贝 :只复制指向某个对象的指针,而不复制对象本身,相当于是新建了一个对象,该对象复制了原对象的指针,新旧对象还是共用一个内存块
  • 深拷贝:是新建一个一模一样的对象,该对象与原对象不共享内存,修改新对象也不会影响原对象

三、赋值与浅拷贝

1.赋值

当我们把一个对象赋值给一个变量的时候,赋值的其实是该对象的栈内存地址而不是堆内存数据。也就是赋值前的对象和赋值后的对象两个对象共用一个存储空间(赋值的是栈内存地址,而该地址指向了同一个堆内存空间),所以,无论哪个对象发生改变,改变的都是同一个堆堆内存空间。因此,无论修改哪个对象对另一个对象都是有影响的。

    var objA ={
        name:'张三',
        age:8,
        pal:['王五','王六','王七']
    }
    var objB = objA
    objB.name = '李四'
    objB.pal[0] = '王麻子'
    console.log('objA.name',objA.name) //李四
    console.log('objB.name',objB.name)//李四
    console.log('objB.pal',objB.pal)//['王麻子','王六','王七']
    console.log('objA.pal',objA.pal)//['王麻子','王六','王七']

总结:赋值后的对象objB改变,原对象objA的值也改变,因为赋值后的对象objB赋值的是原对象objA的栈内存地址,他们指向的是同一个堆内存数据,所以对赋值后的对象objB对数据进行操作会改变公共的堆内存中的数据,所以原对象的值也改变了。

2.浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原对象属性值的一份精准拷贝,如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

有点抽象,来看个例子,该例子也是手写浅拷贝的方法。

    var obj1 ={
     name:'张三',
     age:8,
     pal:['王五','王六','王七']
    }
    var obj3 = shallowCopy(obj1)
    function shallowCopy (src){
     var newObj = {};
     for(var prop in src ){
         console.log(prop)
         if(src.hasOwnProperty(prop)){
             newObj[prop] = src[prop]
         }
     }
     return newObj
    }
     obj3.name = '李四'
     obj3.pal[0] = '王麻子'
       
    console.log("obj1", obj1); //{age: 8, name: "张三", pal: ['王麻子', '王六', '王七']}
    console.log("obj3", obj3); //{age: 8, name: "李四", pal: ['王麻子', '王六', '王七']}

obj3改变了基本类型的值name,并没有使原对象obj1的name改变,obj3改变了引用类型的值,导致原对象的值也改变了。

总结:

  • 赋值:就是对原对象的栈内存地址进行复制
  • 浅拷贝:是对原对象的属性值进行精准复制,如果原对象的属性值是基本类型,那就是值的引用,所以浅拷贝后修改基本类型不会修改到原对象的,如果原对象属性值是引用类型,那么就是对引用类型属性值的栈内存的复制,所以修改引用类型属性值的时候会修改到原对象。
  • 因此一般对无引用类型的属性的兑现拷贝的时候使用浅拷贝就行,对复杂对象包含引用类型属性的时候使用深拷贝。

四、浅拷贝的实现方式

1.Object.assign()

对于Object.assign()而言, 如果对象的属性值为简单类型(string, number),得到的新对象为‘深拷贝’;如果属性值为对象或其它引用类型,那对于这个对象而言其实是浅拷贝的。

    var obj1 = {
      name: '张三',
      age: '25',
      info: {
        address: '北京市朝阳区',
        phone: '13888888888'
      }
    };

    var obj2 = Object.assign({}, obj1);
    obj2.name = '李四';
    obj2.info.phone = '13999999999';
    console.log(obj1, 'obj1');
    console.log(obj2, 'obj2');

注:Object.assign({}, obj1, obj2); 对于obj1和obj2之间相同的属性是直接覆盖的,如果属性值为对象,是不会对对象之间的属性进行合并的。

 

2、es6的对象展开运算符

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

    let a = {
      name: '张三',
      age: '18',
      info: {
        mobile: '17521315204',
        addres: '小张村'
      }
    }
    
    let b = { ...a };
    b.name = '李四';
    b.info.mobile = '18888888888';

    console.log('a:', a);
    console.log('b:', b);

3、对于数组的浅拷贝:slice()和concat()

使用slice、concat方法并不是真正的深拷贝,它们只会深拷贝第一层,而到了第二层及其之后,只会复制引用地址了!

 

五、深拷贝的实现

1、通过JSON序列化实现深拷贝

序列化后再反序列化。

JSON.parse(JSON.stringify(obj))

注意,这种方法容易出很多问题,实际并不常用!

  • 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象。
  • 如果obj里面有时间对象,则序列化之后时间会是字符串的形式,而不是时间对象。
  • 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失。
  • 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null。
  • 无法处理function,无法处理循环引用对象。

2、lodash函数库实现深拷贝

var abj={
  a:1,
  b:2
}

var copyobj = lodash.cloneDeep(abj);

3、手写深拷贝

const deepClone = (obj) => {
  // 是对象吗?是就新建对象开始复制
  if (typeof obj === 'object' && obj != null) {
    // 是对象,我们进一步确定是数组还是{}
    const newObj = Array.isArray(obj) ? [] : {};
    for (let i in obj) {
      // 不管是不是对象,直接递归,外面的typeof会帮我们做判断是否要继续遍历
      newObj[i] = deepClone(obj[i]);
    };
    return newObj;

  } else {
    // 不是对象?直接返回
    return obj;
  }
};

测试一下:

    // 测试
    const obj = {
      name: '听风',
      age: 29,
      other: {
        gender: 'male',
        arr: [1, 2, 3]
      }
    };

    const o = deepClone(obj);
    obj.other.gender = null;
    obj.other.arr[0] = 5;
    console.log(obj, o)

打印结果:

递归方法实现深度克隆原理:

  • 遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
  • 先判断各个字段类型,然后用递归解决嵌套数据。 判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝。 进行深拷贝的不能为空,并且是对象或者是数组。
posted @ 2022-07-27 18:36  小阿飞ZJF  阅读(85)  评论(0编辑  收藏  举报