在 JavaScript 中,堆(Heap)和栈(Stack)是内存管理中两个重要的概念。它们的区别主要体现在数据的存储方式和用途上:
1. 栈(Stack)
栈是一种后进先出(LIFO,Last In First Out)的数据结构,通常用于存储临时数据以及函数调用相关的信息。
特点:
- 存储内容:存储原始值(如
number
,boolean
,undefined
,null
,symbol
,bigint
)以及对引用值的指针。 - 分配方式:自动分配内存,操作速度非常快。
- 存储区域:固定大小的连续内存区域,适合存储小的、大小固定的数据。
- 清除:当函数执行完毕,栈中的数据会自动清除(例如局部变量)。
示例:
function foo() {
let a = 10; // 原始值存储在栈中
let b = 20;
return a + b;
}
foo();
在上面的例子中,a
和 b
是局部变量,存储在栈中。当函数执行完毕后,它们会被自动回收。
2. 堆(Heap)
堆是一个更大的内存池,用于存储对象等复杂的数据结构。
特点:
- 存储内容:存储引用类型的数据(如
Object
,Array
,Function
)。 - 分配方式:动态分配内存,操作速度较慢,但能存储大且灵活的数据。
- 存储区域:不需要连续的内存空间,可以扩展。
- 清除:需要垃圾回收机制(如 V8 引擎中的 GC)定期清理不再使用的对象。
示例:
function bar() {
let obj = { name: "JavaScript" }; // 对象存储在堆中
let arr = [1, 2, 3]; // 数组存储在堆中
return obj.name;
}
bar();
在上面的例子中,obj
和 arr
是引用类型,它们的值存储在堆中,而变量 obj
和 arr
存储的是指向堆中数据的引用(指针)。
3. 堆和栈的对比
特性 | 栈 | 堆 |
---|---|---|
存储内容 | 原始值、指针 | 引用类型的数据 |
内存大小 | 通常较小,固定大小 | 较大,可动态扩展 |
分配方式 | 自动分配 | 动态分配 |
访问速度 | 快 | 慢 |
回收机制 | 函数执行完毕自动回收 | 依赖垃圾回收机制 |
4. 代码中如何体现栈和堆的差异
function test() {
let x = 42; // x 的值存储在栈中
let y = { key: "value" }; // y 的引用存储在栈中,实际对象存储在堆中
let z = y; // z 是一个新指针,指向堆中的同一个对象
z.key = "newValue"; // 修改堆中的对象,y 和 z 都能看到变化
}
test();
在这段代码中:
x
是一个原始值,存在栈中。y
是一个对象引用,引用地址存储在栈中,实际对象存储在堆中。z
和y
指向相同的堆地址,因此修改堆中对象的值会反映在所有指针上。
5. 垃圾回收机制
堆中的数据不会自动清理,需要通过垃圾回收机制来清理不再使用的内存空间。例如,在 JavaScript 的 V8 引擎中,常用的垃圾回收算法有:
- 标记-清除(Mark-and-Sweep):标记活跃对象,清理未被引用的对象。
- 分代回收(Generational GC):将内存分为新生代和老生代,分别处理短期和长期存活的数据。