从值拷贝到深拷贝:彻底弄懂 JavaScript 的堆与栈

彻底搞懂 JS 的堆内存与栈内存:从值拷贝到深拷贝

在学习 JavaScript 的过程中,经常会遇到一些令人困惑的问题:

  • 为什么我改了一个对象,另一个变量也变了?
  • 为什么有的赋值是"独立"的,有的却会互相影响?
  • 深拷贝和浅拷贝,到底区别在哪?

这些现象的根源,其实都来自 ------ 堆内存(Heap)与栈内存(Stack) 的不同存储机制。

本文将带你从内存模型出发,搞懂数据的"居住位置"和"传递方式",彻底弄清值拷贝与引用拷贝的差异。


一、栈内存与堆内存:存储机制的区别

JavaScript 会根据变量类型,选择不同的存储方式:

数据类型 存储位置 特点
基本类型(Number、String、Boolean、null、undefined、Symbol、BigInt) 栈内存(Stack) 连续存储、读写高效、空间固定
引用类型(Object、Array、Function) 堆内存(Heap) 动态分配、可扩展、访问间接

栈内存:简单变量的"快递柜"

ini 复制代码
let a = 1;
let b = 2;
let c = 3;
let d = a; // 值拷贝

栈内存中存储的是值本身 ,且空间连续。
d = a 实际上是对 1 这个值的复制。

因此,无论之后如何修改 a,都不会影响到 d

关键特征:

每个变量都有自己的小空间,互不干扰、读取极快。


堆内存:对象与数组的"仓库区"

bash 复制代码
const users = [
  { id: 1, name: "oumasyu", hometown: "赣州" },
  { id: 2, name: "inx177", hometown: "南昌" },
  { id: 3, name: "gustt_", hometown: "赣州" }
];

数组和对象属于引用类型

它们的实际数据存放在堆内存中,而变量 users 只是保存了一个引用地址

当你执行:

ini 复制代码
const data = users;
data[0].hobbies = ["篮球", "看烟花"];
console.log(data, users);

结果会发现 ------ users 也被改动了!

这是因为:

  • usersdata 在栈中存放的地址相同
  • 它们同时指向堆内存中同一块数据区域
  • 改变任何一方,其实都在修改那块共享的堆空间。

二、引用式拷贝:看似复制,实则共用

可以把这种情况理解为:

bash 复制代码
users ──► [ { id:1, name:"oumasyu" } ]
   ▲
   │(共用同一地址)
data ┘

datausers 并没有创建两份数据,只是共用一个引用。

所以,修改 data[0] 的属性,等同于修改 users[0]


三、想要真正"分家"?你需要深拷贝

如果希望两个对象互不影响 ,就必须让它们在堆内存中拥有各自的空间

方法一:JSON.parse(JSON.stringify())

ini 复制代码
const data = JSON.parse(JSON.stringify(users));
data[0].hobbies = ["篮球", "看烟花"];
console.log(data, users);

执行后你会发现:

修改 data 不会再影响 users ------ 它们终于"分家"了。

原理

通过序列化和反序列化,将对象转成字符串再重新生成,从而创建一份全新的数据结构。

优点:简单直接、常用于深拷贝。

缺点:无法拷贝函数、undefinedSymbol、循环引用等。


方法二:structuredClone()(更现代)

ini 复制代码
const data = structuredClone(users);

structuredClone() 是浏览器原生的深拷贝 API。

相比 JSON 方法,它支持更多数据类型(如 DateRegExpMapSet、循环引用等),

是未来更推荐的写法。


四、图解内存变化:从共享到独立

bash 复制代码
# 引用式拷贝
users ──► [ { id:1, name:"oumasyu" } ]
   ▲
   │
data ┘  (共用同一堆空间)

# 深拷贝后
users ──► [ { id:1, name:"oumasyu" } ]
data  ──► [ { id:1, name:"oumasyu", hobbies:["篮球"] } ]
(独立的两份堆内存数据)

五、核心对比总结

拷贝类型 是否新建堆内存 是否共享数据 常见实现方式
值拷贝(基本类型) =
引用拷贝(对象/数组) =
深拷贝 JSON.parse(JSON.stringify()) / structuredClone()

总结

理解堆内存与栈内存的本质,是写好 JS 的关键一步。

当你清楚变量"指的是什么",你就能轻松判断:

  • 哪些修改会相互影响;
  • 何时该用深拷贝;
  • 如何优化内存和性能。

一句话总结:

基本类型复制的是值,引用类型复制的是地址。

想要真正"断开关系",就得创建新的堆内存。

相关推荐
BBB努力学习程序设计2 小时前
CSS3选项卡:纯CSS实现优雅的内容切换
前端·html
有点笨的蛋2 小时前
从零掌握 Ajax:一次请求带你读懂异步数据加载原理
前端·javascript·ajax
进击的野人2 小时前
JavaScript日期操作与DOM节点管理:构建动态网页的核心技术
前端·javascript
BBB努力学习程序设计2 小时前
Canvas入门指南:从零开始绘制你的第一个图形
前端·html
AAA简单玩转程序设计2 小时前
JS防抖:别再让按钮“手抖”连点了!
前端·javascript·html
晚夏_八月2 小时前
ES6 模块导出 export default 与 export 的区别?
前端
皮蛋瘦肉粥_1212 小时前
pink老师html5+css3day09
前端·css3·html5
Mintopia2 小时前
🧠 可定制化 AIGC:Web 用户个性化模型训练的技术门槛正在塌缩!
前端·人工智能·trae
JarvanMo2 小时前
Flutter CI/CD 完整指南:从 Bitbucket Pipelines 到 Play Store 自动化部署
前端