在 JavaScript 中,数据分为两大类:
- 基本类型(Primitive Type) :
number,string,boolean,null,undefined,symbol,bigint - 引用类型(Reference Type) :
object、array、function等
这并不是语法上的随意区分,而是和 底层内存结构、性能优化、垃圾回收 深度相关。
| 特性 | 基本类型 | 引用类型 |
|---|---|---|
| 存储位置 | 栈 | 堆(栈里存指针) |
| 大小 | 确定(数字、布尔、指针大小固定) | 不确定(对象、数组可能无限扩展) |
| 可变性 | 不可变(string 改了就是新值) | 可变(属性可随时增加/删除) |
| 拷贝方式 | 复制值 | 复制地址(引用) |
| 生命周期 | 随函数栈帧销毁 | GC 负责清理 |
为什么有这两种数据结构?
从语言设计和底层实现角度看:
1. 性能考虑
- 栈:内存连续,大小固定,CPU 偏移量寻址 → 极快。
- 堆:可变长存储,灵活,但分配/回收需要 GC,性能较慢。
2. 功能需求
- 基本类型:小而固定的值,不可变,适合存在栈。
- 引用类型:可变,对象可能无限扩展,必须存在堆。。
3. 内存安全
- 基本类型生命周期清晰:随栈帧回收。
- 引用类型依赖 GC(垃圾回收机制):避免内存泄漏。
栈与堆的工作方式
栈(Stack)
- 先进后出(LIFO) ,内存连续。
- 每次函数调用会创建一个 栈帧:保存参数、局部变量、返回地址。
- 函数执行完,整个栈帧一次性销毁。
堆(Heap)
- 内存分配不连续,大小不固定。
- 存放:对象、数组、函数。
- 生命周期不确定,由垃圾回收机制(标记清除/分代回收)管理。
为什么栈中元素大小必须固定?
-
栈需要 连续内存,CPU 通过栈指针 + 偏移量定位。
-
如果大小不固定,就没法计算偏移量。
-
在 JS 引擎实现里:
- 小整数(SMI)直接存在栈里。
- 复杂对象统一存放一个「固定大小的指针」。
👉 所以栈的数据有规律、固定大小,而堆负责存储那些大小不确定的真实对象