浅拷贝与深拷贝

69 阅读3分钟

js中数据的存储机制

了解浅拷贝和深拷贝得从js中数据在内存中的存储方式说起。在js中基本数据类型是直接存在栈中,但是引用数据类型是储存在堆中,地址是存在栈中。在访问一个引用数据类型时,是先去获取这个数据在栈中的储存的地址,然后根据地址去堆中获取数据。

根据以上的描述在拷贝数据时就会出现一个问题

const obj1 = { a: 1, b: 2 }
const obj2 = obj1

在进行这样的拷贝数据时,其实只是将obj1存储在栈中的地址给赋予了obj2,这样两个变量其实指向的是同一组数据。只要通过其中一个变量去修改数据,另外一个变量去访问数据得到的也是修改后的数据。这样在咱们写代码的时候不注意就会导致数据会在我们没有注意到的地方就被修改了。

const obj1 = { a: 1, b: 2 }
const obj2 = obj1
obj1.a = 2
console.log(obj2.a) // 2

所以在实际编写工作中咱们为了防止上面这种情况的发生就需要使用到浅拷贝和深拷贝

浅拷贝

最简洁的浅拷贝的方法就是使用ES6提供的结构赋值

const obj1 = { a: 1, b: 2 }
const obj2 = { ...obj1 }
obj1.a = 3
console.log(obj2.a) //1

这样写就是将obj2赋值为一个新的{},这里就已经重新在堆区域开辟了一个空间为obj2储存数据以及一个新地址,然后将obj1的值放入了obj2中,这样obj1和obj2指向的就不是同一组数据了。
但是当obj1其中的一个属性也为引用数据类型,通过这种方法进行拷贝数据就会出现问题了。

const obj1 = { a: 1, b: 2, c: { d: 3} }
const obj2 = { ...obj1 }
obj1.c.d = 4
console.log(obj2.c.d) //4

浅拷贝就是指像这样没有将变量中所有的引用数据类型重新在内存中开辟空间储存,只是对第一层的数据进行了存储空间的重新分配。

深拷贝

为了解决上述情况的发生就需要用到深拷贝了,需要去检查在变量中是否嵌套了引用数据类型,嵌套的引用数据类型是否还嵌套了引用数据类型。
最简单的深拷贝的方式就是使用通过将变量转换为JSON格式,再转换回来

const obj1 = { a: 1, b: 2, c: { d: 3} }
const obj2 = JSON.parse(JSON.stringify(obj1))
obj1.c.d = 4
console.log(obj2.c.d) //3

但是通过这种方式会有问题,比较常见的就是当数据内有正则表达式会变为空对象,函数会变为null。 比较方便的做法就是直接引用第三方库lodash提供的_.cloneDeep。
或者自己手写一个递归函数

function deepCopy(obj, cache = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj //原始值
  if (obj instanceof Date) return new Date(obj) //日期值
  if (obj instanceof RegExp) return new RegExp(obj) //正则

  if (cache.has(obj)) return cache.get(obj) //防止循环引用情况
  let copyObj = new obj.constructor() //创建一个和obj类型一样的对象
  cache.set(obj, copyObj) //放入缓存中
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) { //不去查找原型上是否拥有这个属性
      copyObj[key] = deepCopy(obj[key], cache)
    }
  }
  return copyObj
}