在理解JS的深浅复制之前,我觉得有必要先提及一下关于值传递与引用传递。
在JS中,基本类型值的复制是按值传递的,而引用类型值的复制则是按引用传递的。值传递复制的对象间不会有任何牵连,互相独立;但是引用传递复制的对象间则会相互影响,修改其中任何一方的值都会在另一方中体现。之所以会有这样的表现和JS的内存机制有关。
JS的内存也分为堆和栈,注意这里的堆栈和数据结构的堆栈是不同的概念。
栈:由系统自动分配,自动回收,效率高,但容量小
堆:由程序员手动分配内存,并且手动销毁(高级语言如JS中有垃圾自动回收机制),效率不如栈,但容量大
JS定义的基本类型值会被存放在栈中,而引用类型因为其大小不固定,系统会为引用类型分配堆内存空间存放,而只将指向该堆内存空间的指针(即引用)存放在栈中。这样一来,我们访问引用类型值时,实质上只是在访问它的引用,然后再按照这个引用地址去堆中找到它的实际内容。
所以当复制的时候,对于基本类型值变量,系统会为新变量单独开辟一个新的栈内存空间,并将源变量的值复制一份保存到里面。而对于引用类型值,新变量复制得到的只是引用对象的内存地址,这么一来,通过两个变量的引用访问到的实质就是同一个引用类型对象了。所以才会出现对一方引用类型值的修改导致了另一方访问到的引用类型值也发生了同步的变化这样的情况。
浅复制是指在复制对象的时候,只对对象的第一层键值进行复制。
在上面的例子当中,如果不希望修改 new_person 对象的 name 值的时候,源对象的 name 值也跟着一起改变,那么我们可以尝试对复制过程做一些处理,而不再是直接的赋值拷贝。
这时候我们修改了复制对象的 name 值,源对象的 name 不会再跟着改变了,但是当我们修改属性 sport 的值的时候,源对象的 sport 却又跟着改变了。
前面也说了,我们的浅复制只是对第一层键值进行的复制,当源对象内部还嵌套着其它的对象的时候,又会出现一开始遇到的情况了。复制对象的 love 属性复制的是源对象 love 属性所对应对象的地址,所以也导致了复制对象和源对象的 love 属性指向的都是堆内存中同一块内存地址。Object.assign( )方法所实现的也是浅复制。
那如何才能完全独立的复制出一份呢?其实只要递归下去,对内部属性的值仍是对象的再次进入对象内部对其属性值一一复制即可。
对象深复制还可以借助 JSON 实现。
但是这种方式实现的深复制会忽略掉值为 undefined 和 函数表达式 的属性。