深拷贝与浅拷贝

最近工作中一个简单的赋值,引发了一个很奇怪的 bug。所以深拷贝和浅拷贝还是非常有必要弄清楚的。

1.首先看看 js 基本数据类型和引用类型的区别

  • 基本数据类型:String , Number, Boolean, Null, Undefined 存放在栈内存中。直接按值存放,可以直接访问。
  • 引用数据类型:Array, Object, Function 等;存放在堆内存中,变量实际保存是保存在栈中的一个地址,指向堆中具体的位置。从栈中获取地址再从堆内存中取到数据。

2.赋值,拷贝

  • 基本数据类型拷贝的时候,在堆内存中开辟了新的空间,和原来的变量互不相干。不用考虑深拷贝浅拷贝的问题。
  • 引用数据类型在用=号赋值时,实际上是将栈中的地址赋值给新的变量。所以当改变这个新的变量里的数据时,指向的堆中的数据也是会有变化的。(我那个 bug 也就是这个问题)
1
2
3
4
5
let a = 123
let b = a
a = 1234
console.log(a) // 1234
console.log(b) // 123

基本数据类型复制不会发生引用, 栈中会开辟新的空间存放

1
2
3
4
5
let arr = [1, 2, 3]
let arr1 = arr
arr[0] = 3
console.log(arr) // [3, 2, 3]
console.log(arr1) // [3, 2, 3]

引用数据类型复制是将栈中的地址赋值给变量,指向堆中的同一个空间,所以当改变其中一个时,其实是改变堆中的数据,所有指向这个空间的变量都会发生改变。

3.深拷贝和浅拷贝

  • 上面的例子其实就是浅拷贝,引用类型的数据复制的是指向地址。

  • 深拷贝,堆开辟一个新的空间存放复制的对象。

引用类型的浅拷贝会出现很多问题,那么下面来看看引用类型如何实现深拷贝。

4.实现深拷贝的方法

  1. 我最常用的一个是(简单粗暴)序列化反序列化法
1
JSON.parse(JSON.stringify(obj))

但这个方法存在一些问题 :

  • 无法复制正则表达式类型、函数类型
  • 无论构造器是什么都会变成 Object
  • undefined 无法复制
  • 只能深拷贝对象和数组其他类型会失真

但实际开发中只是复制数据是够用的

  1. 迭代递归法

对对象进行迭代操作,对它的每个值进行递归深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function deepClone(obj) {
let copy
if (null == obj || 'object' != typeof obj) return obj
// Date
if (obj instanceof Date) {
copy = new Date()
copy.setTime(obj.getTime())
return copy
}
// Array
if (obj instanceof Array) {
copy = []
for (var i = 0; i < obj.length; i++) {
copy[i] = clone(obj[i])
}
return copy
}
// Object
if (obj instanceof Object) {
copy = {}
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = clone(obj[key])
}
}
return copy
}
throw new Error('error')
}
  1. Object.assign() ES6 的深拷贝方法
  2. concat 对数组进行深拷贝

应该还有很多方法 或者更深入的思考 关于可枚举不可枚举这些概念我还不太清楚 有待进步

------ 本文结束------
0%