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

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

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

一句话总结:

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

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

相关推荐
兆子龙11 分钟前
TypeScript高级类型编程:从入门到精通
前端·后端
SuperEugene14 分钟前
Vue3 模板语法规范实战:v-if/v-for 不混用 + 表达式精简,避坑指南|Vue 组件与模板规范篇
开发语言·前端·javascript·vue.js·前端框架
IT_陈寒20 分钟前
Python开发者的效率革命:这5个技巧让你的代码提速50%!
前端·人工智能·后端
Luna-player21 分钟前
Vue 3 + Vue Router 的路由配置,简单示例
前端·javascript·vue.js
用户693717500138421 分钟前
不卷AI速度,我卷自己的从容——北京程序员手记
android·前端·人工智能
xiaotao13128 分钟前
03. 原子化 CSS 思想
前端·css·tailwind
敲代码的约德尔人32 分钟前
JavaScript 设计模式完全指南
javascript·设计模式
angerdream34 分钟前
最新版vue3+TypeScript开发入门到实战教程之Vue3详解props
javascript·vue.js
小小亮0140 分钟前
qiankun的面试题
前端