【OOP】对象的深拷贝(Deep Copy)与浅拷贝(Shallow Copy)

Posted by 西维蜀黍 on 2019-01-07, Last Modified on 2021-10-16

数据类型

数据分为基本数据类型(String,Number,Boolean,Null,Undefined,Symbol)和对象数据类型。

  • 基本数据类型的特点:直接存储在栈(stack)中的数据
  • 引用数据类型的特点:存储的是该对象在栈(Stack)中引用,真实的数据存放在堆(Heap)内存里

引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

浅拷贝(Shallow Copy)与深拷贝(Deep Copy)

深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。而对于值类型而言,所有的拷贝都是深拷贝。

浅拷贝(Shallow Copy)

浅拷贝会基于原对象,在堆或者栈中实例化一个新的对象,而对于该新对象中的字段(field)为不同数据类型时,会进行不同的处理:

  • 当字段为基本数据类型,该字段会被复制一份,并作为新对象的对应字段。
  • 当字段为对象数据类型,该对象在堆中的地址会被复制,并作为新对象的对应字段的值(此过程中,不存在新对象的实例化)。因此,新旧对象对应的这个字段都指向堆内存中同一个区域。

深拷贝(Deep Copy)

与浅拷贝类似,深拷贝也会基于原对象,在堆或者栈中实例化一个新的对象。而不同点在,对于该新对象中的字段(field)为不同数据类型时,都会进行同样的处理,即:

  • 当字段为基本数据类型,该字段会被复制一份,并作为新对象的对应字段。
  • 当字段为对象数据类型,该字段指向的对象会被重新创建一个,新创建的对象副本(在堆中的地址)作为新对象的对应字段的值

赋值和浅拷贝的区别

赋值

当我们把一个对象(或称为”引用数据类型“)赋值给一个新的变量时,赋的其实是该对象指向于堆中的那个地址值,而不是堆中的数据。也就是两个对象指向的是同一个存储在堆的内存空间。

因此,无论引用哪个变量,其实都是改变的这个内存空间的内容。所以,两个对象是联动的。

浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。

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

例子

对象赋值

// 对象赋值
 var obj1 = {
    'name' : 'zhangsan',
    'age' :  '18',
    'language' : [1,[2,3],[4,5]],
};
var obj2 = obj1;
obj2.name = "lisi";
obj2.language[1] = ["二","三"];
console.log('obj1',obj1)
console.log('obj2',obj2)

运行结果

分析

可以看到,由于 var obj2 = obj1; 的本质是将obj1所指向的对象的内存地址赋值给obj2,因此无论使用obj1还是obj2,本质上都是在操作同一块堆内存。

浅拷贝

情况1

// 浅拷贝1
var obj1 = {
    'name' : 'zhangsan',
    'age' :  '18',
    'language' : [1,[2,3],[4,5]],
};
var obj3 = shallowCopy(obj1);

obj3.name = "lisi";
obj3.language = ["一","二","三"];

console.log('obj1',obj1)
console.log('obj3',obj3)

function shallowCopy(src) {
   var dst = {};
   for (var prop in src) {
       if (src.hasOwnProperty(prop)) {
           dst[prop] = src[prop];
       }
   }
   return dst;
}

运行结果

分析

对于 obj3.language = ["一","二","三"];而言,obj3.language是一个引用数据类型。因此,最终obj3对象的language字段会指向["一","二","三"]所在的堆内存。而obj1对象的language字段仍然指向[1,[2,3],[4,5]]所在的堆内存,因此值不会被改变。

情况2

// 浅拷贝2
 var obj1 = {
    'name' : 'zhangsan',
    'age' :  '18',
    'language' : [1,[2,3],[4,5]],
};
 var obj3 = shallowCopy(obj1);
 obj3.name = "lisi";
 obj3.language[1] = ["二","三"];
 function shallowCopy(src) {
    var dst = {};
    for (var prop in src) {
        if (src.hasOwnProperty(prop)) {
            dst[prop] = src[prop];
        }
    }
    return dst;
}
console.log('obj1',obj1)
console.log('obj3',obj3)

运行结果

分析

对于 obj3.language[1] = ["二","三"];

  • 由于language字段为一个引用数据类型,因此obj1obj3language字段均指向同一个堆内存地址。

  • 所以obj1.language[1]obj3.language[1]均指向同一个堆内存地址。

  • 最终,obj3.language[1] = ["二","三"];的执行会改变obj1.language[1]obj3.language[1]的值。

Reference