js里的数据类型分为基本数据类型和引用数据类型,基本数据类型有undefined
, boolean
, number
, string
, null
, Symbol
(ES6中新增),而引用数据类型就是Object
。两种类型在赋值上面是有一些区别的。
赋值
基本数据类型
基本数据类型的赋值操作是直接拷贝一份原数据的副本,赋值后改变原数据的值并不会影响到副本数据的值,反之也不会相互影响。
1 | var a = 1 |
引用数据类型
而引用数据就不同了,赋值操作后改变原数据和副本数据任何一个数据的值都会影响另一个数据的值。这是因为在将一个引用数据的值赋值给副本数据后,副本数据保存的是和原引用数据相同(以下简称原数据)的指针,该指针和原数据指向同样的内存地址。其中任何一方修改这个指针的值,内存地址对应存储空间上的值发生改变后,另一方的指针指向的位置不变,所以对应指向的值也就发生了相同的改变。
1 | var a = { |
因为引用数据的特性,这让我们在想进行拷贝引用数据操作时发生了问题。如果想拷贝引用数据,修改副本数据时不影响原数据的话,就要用到拷贝方法。拷贝方法又分为浅拷贝和深拷贝。
浅拷贝
遍历实现浅拷贝
1 | // 我们先来创建一个a对象 |
但是浅拷贝方法也有一个问题,继续看:
1 | // 重新创建一个c对象 |
这时修改原数据c的值,副本数据d也一起被修改了。这是什么原因呢?原来是因为浅拷贝操作只能复制一层对象的属性,并不包括对象里的引用数据类型的值,所以当修改原数据c里的数组的值后,浅拷贝的副本数据d的数组值也就一起被改变了。如果要完全独立拷贝副本数据,就需要用到深拷贝的方法了。
ES6浅拷贝数组方法
ES6新增了扩展运算符...
,可以实现数组的浅拷贝。
1 | // 创建原始数组 |
深拷贝
使用JSON序列化实现深拷贝
深拷贝的一种方法是利用JSON.stringify()
方法,先将原对象转化为json格式,再用JSON.parse()
方法将转化后的json数据转回js对象的方式,进行深拷贝。
1 | // 还是重新创建一个a对象 |
这样,我们就可以用深拷贝的方法拷贝引用数据类型的值,随意修改原数据或副本数据的其中一个,而不用担心另一个也会被随之修改了。
递归实现对象的深拷贝
还有一种递归实现深拷贝的方法。解决思路是先用for...in...
的方法遍历原对象,检测每次遍历的值是否为对象,是的话再次调用自身递归循环遍历,不是的话直接添加。
1 | // 先创建原始a对象 |
这种方法只针对内部全是对象的引用数据类型。如果还想加上数组,需要再加上一层验证。
需要注意的是两种方法都不完美,都不支持值为Function
或正则
等格式的引用数据类型,需要寻找其它的解决方案。要想实现完美的深拷贝还需要考虑怎么处理原型等问题,一般在实际应用当中,基本上是没有全部需要深拷贝的情况的。上面说的两种方法在一般在业务中基本上是足够使用的。