原始类型值存栈中,赋值复制值;引用类型栈存地址、堆存对象,赋值复制地址。typeof null 返回"object"是历史bug,与存储无关。

JavaScript 中原始类型(如 number、string、boolean、null、undefined、symbol、bigint)和引用类型(如 object、array、function、date、regexp 等)在内存中的存储方式不同,但要注意:JavaScript 引擎并不直接暴露“栈”和“堆”的底层实现细节,也没有标准规定必须用栈/堆来实现。不过,从行为表现和主流引擎(如 V8)的实际机制出发,我们可以用“栈区存值、堆区存址”这个模型来理解它们的差异。
原始类型:值直接存在“栈”中
原始类型的变量在声明时,其值本身被分配在调用栈(call stack)对应的内存区域中(V8 中常称为“栈帧”或“上下文”)。这意味着:
- 赋值时是复制值本身,两个变量互不影响
- 生命周期与执行上下文绑定,函数退出后自动释放
- 比较时是按值比较(
===判断是否完全相等)
例如:
let a = 42; let b = a; b = 100; console.log(a); // 42 —— a 没变
引用类型:栈中存地址,真实数据存在“堆”中
引用类型变量在栈中只保存一个指向堆内存中实际对象的引用(指针/地址)。堆(heap)是用于动态分配、生命周期更长的内存区域:
立即学习“Java免费学习笔记(深入)”;
- 赋值时复制的是引用地址,而非对象内容
- 多个变量可能指向同一块堆内存,修改会影响所有引用
- 垃圾回收器(GC)通过可达性分析决定何时回收堆中对象
例如:
let obj1 = { name: "Alice" };
let obj2 = obj1; // obj2 存的是 obj1 指向堆的同一地址
obj2.name = "Bob";
console.log(obj1.name); // "Bob"
为什么 typeof null 返回 "object"?
这和内存存储无关,而是历史遗留 bug。早期 JavaScript 的实现中,null 被设计为全零的机器码表示,而对象指针的低位也恰好为 0,导致类型判断误判为 object。ECMAScript 规范已将其列为不可修复的兼容性事实,不是存储模型的问题。
注意几个常见误解
-
没有“引用类型存在栈中”的情况:即使是一个空对象
{}或字面量数组[],也一定在堆中分配(V8 可能对极小对象做栈上逃逸优化,但对外行为一致) - 闭包中的变量不等于“存在栈中”:被闭包捕获的局部变量,若仍被引用,会被提升到堆中长期持有
- const 声明不改变存储位置:它只是禁止重新赋值,对原始类型是锁值,对引用类型是锁引用地址(对象内部属性仍可修改)










