携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情
前言
前段时间公司招聘小伙伴,在寻找合适的partners的过程中发现了一个问题:有的小伙伴对于深浅拷贝不是很清楚。在日常开发中,碰到的一些灵异事件就有可能是引用类型拷贝不彻底导致的,所以我这里打算详细的针对深浅考虑来唠一下。
适用范围:对深浅拷贝不熟悉或者有疑问的小伙伴
浅拷贝
什么是浅拷贝?
用大白话来说就是,只对深度为1的属性来进行拷贝,对于属性的值直接进行赋值。用官方话来说就是,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
需要特殊说明的是,浅拷贝不等于引用赋值(个人理解,如有错误,烦请斧正)。在我看来,引用赋值是对整个对象的指针的拷贝,新的对象和原始对象完全相等;而对于浅拷贝来说,创建的是一个全新的对象,新的对象和原始对象不相等,而且只有值为引用数据类型的属性才会出现共用内存的情况。
常见的浅拷贝的方法
- Object.assign
- Array.prototype.concat
- ...(解构赋值)
- for循环(非递归)
浅拷贝的特殊
由于浅拷贝对于属性的拷贝是直接引用赋值,这就导致使用浅拷贝的两个数据会存在一些互通,进而产生一些灵异事件。尤其是在进行代码调试,且输出内容为引用数据类型时,需要注意处理。
我明明没有修改对象b,对象b为什么发生了变化呢?
const a = {
name: 'a',
features: {
size: 1,
color: '#333'
}
}
const b = Object.assign({}, a);
a.name = 'newA';
a.features.size = 2;
// b
// {
// name: 'a',
// features: {
// size: 2,
// color: '#333'
// }
// }
深拷贝
深拷贝又怎么说?
深拷贝就是对原始对象进行一次全方位的复制,而不是局限于深度为1的属性。
常见的深拷贝的方法
- JSON.stringify + JSON.parse
- 递归
深拷贝方法的优劣
- JSON.stringify + JSON.parse
对于这种方法来说,优势是使用起来简单方便,可以满足大多数的使用常见;但是劣势也很明显,由于JSON.stringify本身的问题,导致含有特殊数据类型的时候,拷贝出来的新对象跟原始对象不一致。比如:undefined、函数、日期等。
当然,我们可以通过重新JSON.stringify的replacer方法来解决,相对于递归拷贝来说,可读性不高。
- 递归
递归应该是深拷贝的基本操作了,深拷贝的优势是适用于所有数据类型,不会出现不适用的场景。但是深拷贝也有自身的问题,那就是使用姿势不正确会导致栈溢出。
纳尼?深拷贝还会导致栈溢出?
是的,深度优先的递归理论上是会出现的,产生的原因是,由于闭包导致作用域无法及时释放。当然,解决办法也很简单,大致有两种方法:
- 广度优先递归
- 尾递归调用
有关广度优先和深度优先的闲聊我们放到另外一篇文章中进行,这里就不多说了。
结语
好了,看到这里是不是对于深浅拷贝初步有了大致的了解了呢?如果还有其它疑问或者文章中描述不正确的地方,欢迎在下方留言讨论。