从下述例子中我们可以发现,如果给一个变量赋值一个对象,那么两者的值会是同一个引用,其中一方改变,另一方也会相应改变。
通常在开发中我们不希望出现这样的问题,我们可以使用浅拷贝来解决这个问题。
1 | let a = { |
浅拷贝
简单的引用复制
1 | function shallowClone(copyObj) { |
Array的slice和concat方法
Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。它看起来像是深拷贝。而实际上它是浅拷贝。原数组的元素会按照下述规则拷贝:
- 如果该元素是个对象引用 (不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。
- 对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。
如果向两个数组任一中添加了新元素,则另一个不会受到影响。例子如下:
1 | var array = [1,2,3]; |
可以看出,concat和slice返回的不同的数组实例,这与直接的引用复制是不同的。而从另一个例子可以看出Array的concat和slice并不是真正的深复制,数组中的对象元素(Object,Array等)只是复制了引用。如下:
1 | var array = [1, [1,2,3], {name:"array"}]; |
Object.assign()
首先可以通过 Object.assign
来解决这个问题。
1 | let a = { |
展开运算符
当然我们也可以通过展开运算符(…)来解决
1 | let a = { |
通常浅拷贝就能解决大部分问题了,但是当我们遇到如下情况就需要使用到深拷贝了
1 | let a = { |
请注意,Object.assign()
会触发setter,而展开操作符则不会。
浅拷贝只解决了第一层的问题,如果接下去的值中还有对象的话,那么就又回到刚开始的话题了,两者享有相同的引用。要解决这个问题,我们需要引入深拷贝。
深拷贝
JSON对象的parse和stringify
这个问题通常可以通过 JSON.parse(JSON.stringify(object))
来解决。
1 | let a = { |
但是该方法也是有局限性的:
- 会忽略
undefined
- 不能序列化函数
- 不能解决循环引用的对象
1 | let obj = { |
如果你有这么一个循环引用对象,你会发现你不能通过该方法深拷贝
在遇到函数或者 undefined
的时候,该对象也不能正常的序列化
1 | let a = { |
你会发现在上述情况中,该方法会忽略掉函数和 undefined
。
zepto中的深拷贝代码
1 | // 内部方法:用户合并一个或多个对象到第一个对象 |
MessageChannel
但是在通常情况下,复杂数据都是可以序列化的,所以这个函数可以解决大部分问题,并且该函数是内置函数中处理深拷贝性能最快的。当然如果你的数据中含有以上三种情况下,可以使用 lodash 的深拷贝函数。
如果你所需拷贝的对象含有内置类型并且不包含函数,可以使用 MessageChannel
1 | function structuralClone(obj) { |
自己实现深拷贝
1 | function checkType(obj) { |
参考链接: