最近工作中一个简单的赋值,引发了一个很奇怪的 bug。所以深拷贝和浅拷贝还是非常有必要弄清楚的。
1.首先看看 js 基本数据类型和引用类型的区别
- 基本数据类型:String , Number, Boolean, Null, Undefined 存放在栈内存中。直接按值存放,可以直接访问。
- 引用数据类型:Array, Object, Function 等;存放在堆内存中,变量实际保存是保存在栈中的一个地址,指向堆中具体的位置。从栈中获取地址再从堆内存中取到数据。
2.赋值,拷贝
- 基本数据类型拷贝的时候,在堆内存中开辟了新的空间,和原来的变量互不相干。不用考虑深拷贝浅拷贝的问题。
- 引用数据类型在用=号赋值时,实际上是将栈中的地址赋值给新的变量。所以当改变这个新的变量里的数据时,指向的堆中的数据也是会有变化的。(我那个 bug 也就是这个问题)
1 | let a = 123 |
基本数据类型复制不会发生引用, 栈中会开辟新的空间存放
1 | let arr = [1, 2, 3] |
引用数据类型复制是将栈中的地址赋值给变量,指向堆中的同一个空间,所以当改变其中一个时,其实是改变堆中的数据,所有指向这个空间的变量都会发生改变。
3.深拷贝和浅拷贝
上面的例子其实就是浅拷贝,引用类型的数据复制的是指向地址。
深拷贝,堆开辟一个新的空间存放复制的对象。
引用类型的浅拷贝会出现很多问题,那么下面来看看引用类型如何实现深拷贝。
4.实现深拷贝的方法
- 我最常用的一个是(简单粗暴)序列化反序列化法
1 | JSON.parse(JSON.stringify(obj)) |
但这个方法存在一些问题 :
- 无法复制正则表达式类型、函数类型
- 无论构造器是什么都会变成 Object
- undefined 无法复制
- 只能深拷贝对象和数组其他类型会失真
但实际开发中只是复制数据是够用的
- 迭代递归法
对对象进行迭代操作,对它的每个值进行递归深拷贝
1 | function deepClone(obj) { |
- Object.assign() ES6 的深拷贝方法
- concat 对数组进行深拷贝
应该还有很多方法 或者更深入的思考 关于可枚举不可枚举这些概念我还不太清楚 有待进步