我对深拷贝,浅拷贝的理解和总结

214 阅读5分钟

要理解深浅拷贝的区别,首先要知道深浅拷贝最主要的差别就是他们在内存中的存储类型不同,存储类型不同导致存储区域的不同。谈到内存中的存储区域,作为学习计算机的程序员们来说,我们应该非常熟悉,内存可以分为堆和栈,其中栈是系统自动分配的内存空间,并且由系统自动释放;而堆则是动态分配的内存,大小不固定,由程序员自行分配并自行释放。

接着我们再来看数据类型,因为数据类型不同,存储空间也就不同。众所周知,JS中有基本数据类型和引用数据类型。其中基本数据类型有:undefined, boolean, null, number, string, 基本数据类型存放在栈中,数据大小确定,直接按值存放,可以直接访问。这里要特别注意一点,基本类型的值不可变!!!基本类型的值不可变!!!基本类型的值不可变!!!。基本类型的原始值和引用类型比如对象有本质的区别,原始值是不可更改的。任何方法都不能更改一个原始值,字符串中修改方法并不是改变了那个字符串而是返回了一个新的字符串!基本类型值不可更改也就是说不管我们如何动态“修改”这些值,都只是我们以为在原数据上做修改,其实原数值并没有变化,而是返回了一个新的基本类型数据。字符串其实还好理解,那么数字、布尔值呢?我们确实可以给他们赋一个新值啊,乍一看确实很困惑,但是只要想明白内存的运作就好了,就比如说我们定义一个变量mynum,让它等于22,然后我们再重新给它一个值3,最后mynum这个变量的值确实更改为3,但是这里的更改不是原始值22被更改,原始值数字22依然还是22。这是因为我们在栈中为变量mynum申请了一块内存空间,当重新赋值时,该内存空间中的二进制数据被替换成了3对于的二进制数据。这里我们不用过分深究,只需记得基本值是不能更改的就好。基本类型数据的比较是对值的比较,只要值相等就会被认为是相等的。

引用类型数据存放在堆中,学过C++的应该都知道,引用类型的变量存储的不过是一个存放引用类型数据的地址的指针,这个指针存放在栈中,但是却指向堆中某个地址,而该地址下存放着真正的引用类型数据。引用类型数据是可以被改变的,并且引用类型的比较是引用的比较,引用的比较指的是引用对象的指针之间的比较,比较两个引用类型数据,其实就是比较两个存放在栈中的指针是否指向堆中的同一个对象,如果是则返回true,如果不是则返回false。

var a = {name: 'mike'};
var b = {name: 'mike'};
console.log(a === b); // false 

上面这两个对象虽然都存储了相同的信息,但是因为在堆中的地址不同,所以变量a和变量b不是相同的。

搞清楚基本类型和引用类型的差别之后,我们应该能够理解传值和传址的差异了。基本类型的赋值就是在栈中开辟一个新的区域,再将值存到这块区域中。

var a = 1;
var b = a;
a = a + 1;
console.log(a); // 2
console.log(b); // 1

从上面的代码可以看出,基本类型的赋值是对两个独立的变量进行操作,他们之间不会互相影响,修改一个变量的值不会影响到另一个变量,因为他们在栈中存储位置不一样。引用类的赋值则不同,传值靠的是地址的传递,改变的是指针的指向,下面我们看代码。

var a = {};
var b = a;
a.name = 'mike';
console.log(a.name); // "mike"
console.log(b.name); // "mike"
b.age = 23;
console.log(a.age); // 23
console.log(b.age); // 23
console.log(a === b); // true

通过令b=a,变量b或者说指针b也指向了堆中与a指针指向一致的那个空对象,当通过任意一个指针修改那个对象时,都可以生效,这样变量a和变量b就会互相影响。

OK,基于以上这些基础知识的学习,下面我们正式开始讨论浅拷贝和深拷贝。对于简单的赋值操作也要区分数据类型,如果是对基本值的赋值,则是新建了内存空间,对新内存空间的操作不会影响原始数据。而如果是用引用类型赋值,则其实指向同一个堆中的内存区域,修改的是同一个对象,会互相影响。那么浅拷贝呢?浅拷贝对于对象的基本类型的属性值会重新开辟内存空间,从而不会对原始对象产生影响;而对于对象内部的引用类型的属性值则不会重新开辟内存空间,从而会对原始对象产生影响。 那么深拷贝呢?深拷贝相对于浅拷贝更进一步,它会为对象的基本类型属性和子对象都开辟内存空间。