浅复制和深复制都可以实现在已有对象的基础上再生一份对象的作用,但是对象的实例存储都是在堆内存中,然后通过一个引用值去操作对象的,所以复制对象的时候就存在两种情况:复制引用和复制实例,这也是浅复制和深复制的区别所在。
浅复制:浅复制是复制引用,复制后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响
深复制:深复制不是简单的复制引用,而是在堆中重新分配内存,并且把源对象实例的所有属性都进行新建复制,以保证深复制的对象的引用图不包含任何原有对象或对象图上的任何对象,复制后的对象与原来的对象是完全隔离的
简单来说,浅复制只复制一层对象的属性,而深复制则递归复制了所有层级。
浅拷贝js对象
正如上面说的,浅拷贝只拷贝一层对象的属性,因此实现起来是很方便的,下面是一个简单的自己写浅拷贝的代码:
|
|
但是浅拷贝存在一个问题,sourceObj的friends属性是一个引用类型的数组对象,浅拷贝只是实现了targetObj和sourceObj指向同一个引用,如果这时候修改targetOb.friends的值,sourceObj.fiends的值也会受影响:
到现在有些人应该看出来了,要实现targetObj与sourceObj之间没有任何关联,要求如果源对象存在对象属性,那么需要进一步进行一层层递归拷贝,从而保证拷贝的对象与源对象完全隔离。这就是我们所说的深拷贝
深拷贝js对象
JSON对象的parse和stringify
JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,借助这两个方法,也可以实现对象的深复制。
从代码的输出可以看出,复制后的targetObj与sourceObj是完全隔离的,二者不会相互影响。
这个方法使用较为简单,可以满足基本的深复制需求,而且能够处理JSON格式能表示的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深复制(而且会直接丢失相应的值),同时如果对象中存在循环引用的情况也无法正确处理,并且这会抛弃对象的constructor,也就是深复制之后,无论这个对象原本的构造函数是什么,在深复制之后都会变成Object。
首先我们要考虑两个问题,带着这两个问题去创建深度复制函数:
- 对于任何对象,它可能的类型有Boolean, Number, Date, String, RegExp, Array 以及 Object(所有自定义的对象全都继承于Object)
- 我们必须保留对象的构造函数信息(从而使新对象可以使用定义在prototype上的函数)
下面是根据这两个思路编写的简单的深拷贝代码,当然实现深度复制代码的方法有很多种,这只是其中的一种:
|
|
通过看博客发现还有一个比较优美的方法,这里一并贴出来
定义在Object.prototype上的clone()函数是整个方法的核心,对于任意一个非js预定义的对象,都会调用这个函数。而对于所有js预定义的对象,如Number,Array等,就通过一个辅助clone()函数来实现完整的克隆过程。