从值拷贝到深拷贝:彻底弄懂 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 的关键一步。

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

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

一句话总结:

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

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

相关推荐
Dragon Wu1 天前
TailWindCss cva+cn管理样式
前端·css
烤麻辣烫1 天前
Web开发概述
前端·javascript·css·vue.js·html
Front思1 天前
Vue3仿美团实现骑手路线规划
开发语言·前端·javascript
徐同保1 天前
Nano Banana AI 绘画创作前端代码(使用claude code编写)
前端
Ulyanov1 天前
PyVista与Tkinter桌面级3D可视化应用实战
开发语言·前端·python·3d·信息可视化·tkinter·gui开发
计算机程序设计小李同学1 天前
基于Web和Android的漫画阅读平台
java·前端·vue.js·spring boot·后端·uniapp
干前端1 天前
Message组件和Vue3 进阶:手动挂载组件与 Diff 算法深度解析
javascript·vue.js·算法
lkbhua莱克瓦241 天前
HTML与CSS核心概念详解
前端·笔记·html·javaweb
沛沛老爹1 天前
从Web到AI:Agent Skills CI/CD流水线集成实战指南
java·前端·人工智能·ci/cd·架构·llama·rag
和你一起去月球1 天前
动手学Agent应用开发(TS/JS 最简实践指南)
开发语言·javascript·ecmascript·agent·mcp