本质区别是原始类型值直接存栈,引用类型栈存地址、堆存对象;前者赋值复制值,后者复制地址,导致修改行为与共享性截然不同。

JavaScript 中原始类型和引用类型的内存行为,本质是“值直接存栈”和“栈存地址、堆存对象”的区别。这种差异不靠画图也能理解,但可视化能帮你一眼抓住关键——不是讲抽象概念,而是看清变量、地址、数据三者怎么连起来。
原始类型:栈里直接放数字、字符串这些“实打实的值”
声明 let age = 25,引擎就在栈里划出一小块地方,把 25 这个数本身写进去;再写 let copy = age,就再划一块地方,把 25 的副本也写进去。两个位置彼此隔开,改一个不影响另一个。
- 所有原始类型(number、string、boolean、null、undefined、symbol、bigint)都这样处理
- 栈空间小、访问快,适合存结构固定、体积小的值
- 比较用
===是比内容是否一致,比如"a" === "a"得 true,因为栈里两个地方都存着字符 a
引用类型:栈里只记“在哪”,真东西全在堆里
写 let user = { name: "Tom" },栈里只存一个类似 0x1a2b 这样的地址,而 { name: "Tom" } 这整个对象,实实在在地存在堆内存里。后续赋值 let backup = user,只是把那个 0x1a2b 地址又抄了一遍到新栈位——两个变量指向堆里同一块区域。
- 对象、数组、函数、日期、正则等都走这条路
- 堆空间大、动态分配,适合存大小不定、结构复杂的结构
- 修改
backup.name = "Jerry",user.name也变成 Jerry,因为它们查的本就是堆里同一个对象
为什么 typeof null 是 "object"?跟内存模型完全无关
这只是早期 V8 实现的一个历史遗留判断错误:null 在底层用全零位表示,而当时对象指针低位也是 0,导致类型检测误判。规范已将其定为不可更改的兼容事实,既不反映存储方式,也不影响实际行为。
- null 和 undefined 都是原始类型,值直接存在栈中
- 它们和其他原始类型一样,赋值复制的是值本身,不是地址
- 别被
typeof的返回值带偏——它和内存布局、赋值逻辑、比较规则都没关系
常见操作背后的内存动作,一目了然
很多困惑其实来自没分清“变量存什么”和“它指向什么”。比如:
-
const arr = [1, 2]; arr.push(3)—— const 锁住的是栈里那个地址,不是堆里数组内容;所以能改元素,不能重新赋值 -
let a = {}; let b = a; a = null—— b 还指着原来的堆对象,a 只是把自己的栈地址改成了 null -
JSON.parse('{ "x": 1 }')返回的新对象一定在堆里,哪怕内容极小,JS 引擎也不会把它塞进栈











