从值拷贝到深拷贝:彻底弄懂 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 也被改动了!

这是因为:

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

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

可以把这种情况理解为:

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

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

所以,修改 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 ------ 它们终于"分家"了。

原理 :

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

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

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


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

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

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

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

是未来更推荐的写法。


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

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

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

五、核心对比总结

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

总结

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

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

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

一句话总结:

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

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

相关推荐
程序员老赵7 小时前
Docker 部署青龙面板:轻松搭建定时任务管理平台
前端·docker·开源
Swot7 小时前
Asciidoctor 公式数字下标错位问题:原因分析与解决方案
前端
Swot7 小时前
leo-editor 中将 Markdown 内容转成 asciidoc 格式
前端
狒狒AI8 小时前
Remotion / GSAP+Three / HyperFrames / Skia 四栈实测:代码渲染知识视频的选栈方法(附开源技能)
前端·人工智能·aigc
修炼的dance8 小时前
浏览器里导出H.265,8.5秒和55.5秒差在硬件
前端
补码缩写8 小时前
WebCodecs选编码器,探测之后还要验解码和播放
前端
大文说跨境8 小时前
亚马逊多账号管理浏览器怎么选?云登/紫鸟/AdsPower 对比
java·前端·自动化
hunter_0768 小时前
iOS 首页复杂嵌套滚动技术方案:头部固定 + 网格滚动 + Tab 吸顶 + 每 Tab 独立下拉刷新
前端
CoderYanger8 小时前
Java EE 进阶:4.Spring Web MVC 入门(第一弹)
java·开发语言·前端·spring·面试·java-ee·mvc
用户2788607285678 小时前
fallback 会盖掉平台提示词:system、空数组和 systemAppend 的差别
前端·后端