菜鸟进阶之路:一文讲清JS深拷贝和浅拷贝,下次别再无脑用等号辣

66 阅读3分钟

前言

我见过同事写过这样一个错误:在Vue2的环境中,子组件通过Props获取到父组件的传值ObjectA,而他在使用这个ObjectA的时候直接使用了=来将值赋给了另一个变量,这时候问题出现了,明明是在子组件里修改了某个值且不触发事件的情况下竟然修改了父组件中的值。

其实一眼就能看出来问题出在哪里,在Vue中,当父组件将一个对象传递给子组件作为prop时,实际上是将对象的引用传递给了子组件。这意味着在子组件中修改这个对象的属性时,会影响到父组件中相同对象的属性。

为了避免这种情况,应该在子组件中避免直接修改通过props传递的对象。如果需要在子组件中修改这个对象,应该先将其复制一份再进行操作。

如果你也遇到过这样的问题,那你可能也需要来看看这篇对深浅拷贝的介绍了。

image.png

数据类型

在前文中提及了JS的两种数据类型,以下是简要概括:

  • 基本类型数据存储在栈内存中
  • 引用类型数据存储在堆内存中,引用数据类型的变量是指向堆内存中实际对象的引用,在栈中存在。

浅拷贝

浅拷贝指创建一个新的数据,该数据精确复制了原始数据的属性值

如果属性是基本类型,则复制的是基本类型的值。如果属性是引用类型,则复制的是内存地址

浅拷贝只复制一层,深层引用类型则共享内存地址

JavaScript中,存在浅拷贝现象的方法有:

  • Object.assign
  • Array.prototype.slice(), Array.prototype.concat()
  • 使用拓展运算符实现的复制

深拷贝

深拷贝会开辟一个新的栈,两个对象完全相同,但对应不同地址,修改一个对象的属性不会影响另一个对象的属性

常见的深拷贝方法包括:

  • _.cloneDeep() 用深拷贝库

  • jQuery.extend()

  • JSON.stringify() 即 JSON.parse(JSON.stringify(obj)),但会忽略undefinedsymbol函数

  • 手写循环递归

举一个手写循环递归来完成深拷贝的例子

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}

区别

简而言之,浅拷贝和深拷贝都会创建一个新对象,但在复制对象属性时表现不同

浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象仍共享同一块内存,修改对象属性会影响原对象。

而深拷贝会创建一个完全相同的新对象,新旧对象不共享内存,修改新对象不会影响原对象。

总结:

  • 浅拷贝只复制一层,当属性为对象时,浅拷贝是复制,两个对象指向同一地址
  • 深拷贝是递归复制深层次,当属性为对象时,深拷贝会创建新栈,两个对象指向不同地址

结语

至此,JS中的深浅拷贝就已经介绍完了。

欢迎大家留言讨论,不足之处也烦请批评指正。

参考

JavaScript深拷贝的几种方法 - 前端开发随笔 - SegmentFault 思否

吐血整理 js 深浅拷贝问题 - 知乎 (zhihu.com)