JavaScript 深拷贝学习笔记

23 阅读3分钟

JavaScript 深拷贝学习笔记

在 JavaScript 中,数据类型分为基本数据类型(如 numberstringboolean 等)和引用数据类型(如 ObjectArrayFunction 等)。理解这两类数据在内存中的存储方式,是掌握深拷贝与浅拷贝的关键。

一、栈内存 vs 堆内存

  • 基本数据类型存储在栈内存中,其特点是空间固定、访问高效。当我们执行 let d = a; 这样的赋值操作时,实际上是将 a 的值复制一份给 d,两者互不影响。
  • 引用数据类型(如对象、数组)则存储在堆内存中,变量本身(如 users)在栈中仅保存一个指向堆内存的引用地址。因此,当执行 const data = users; 时,并没有创建新的对象,而是让 datausers 共享同一个堆内存地址。对 data 的修改会直接影响 users,这就是浅拷贝的本质。

二、浅拷贝的问题

const users = [
  { Id: 1, Name: '张三', hometown: '北京' },
  { Id: 2, Name: '李四', hometown: '北京' }
];

const data = users; // 浅拷贝(引用赋值)
data[0].hobbies = ["篮球", "看烟花"];
console.log(users); // hobbies 已被修改!

上述代码中,datausers 指向同一块堆内存,任何一方的修改都会影响另一方。这在实际开发中极易引发难以追踪的 bug。

三、什么是深拷贝?

深拷贝是指:创建一个全新的对象,递归地复制原对象的所有层级属性,新旧对象在内存中完全独立,互不影响。

四、使用 JSON 方法实现深拷贝

一种简单且常用的深拷贝方法是利用 JSON.stringify()JSON.parse()

const users = [
  { Id: 1, Name: '张三', hometown: '北京' },
  { Id: 2, Name: '李四', hometown: '北京' }
];

const data = JSON.parse(JSON.stringify(users)); // 深拷贝
data[0].hobbies = ["篮球", "看烟花"];
console.log(data);  // 包含 hobbies
console.log(users); // 不受影响,无 hobbies

原理:

  1. JSON.stringify(users) 将对象序列化为字符串(此时脱离了引用关系);
  2. JSON.parse(...) 将字符串反序列化为一个全新的对象,存入新的堆内存空间。

优点:

  • 实现简单,一行代码即可完成;
  • 适用于大多数纯数据对象(不含函数、undefined、Symbol 等)。

缺点:

  • 无法处理函数、undefined、Symbol、Date、RegExp 等特殊类型
  • 无法处理循环引用(会导致报错);
  • 会丢失原型链信息(拷贝后对象的 constructor 变为 Object)。

五、何时使用深拷贝?

  • 需要对复杂对象进行“安全”的修改而不影响原始数据;
  • 在状态管理(如 Redux)、表单编辑、数据快照等场景中非常常见;
  • 传递数据给第三方库或 API 前,避免意外副作用。

六、其他深拷贝方案(补充)

对于更复杂的场景,可考虑:

  • 使用 Lodash 的 _.cloneDeep()
  • 手写递归深拷贝函数(需处理各种边界情况);
  • 使用 structuredClone()(现代浏览器支持,ES2022 新增)。

总结

深拷贝是 JavaScript 开发中绕不开的重要概念。通过 JSON.stringify + JSON.parse 虽然不能覆盖所有情况,但在处理普通 JSON 数据时高效可靠。理解栈与堆内存的区别、引用赋值的本质,是写出健壮代码的基础。在实际项目中,应根据数据结构的复杂度选择合适的拷贝策略,避免因浅拷贝导致的数据污染问题。

记住:当你不确定是否需要深拷贝时,先问自己——“修改这个副本,会不会影响原始数据?” 如果答案是“不希望影响”,那就用深拷贝!