在我们编写 JavaScript 代码时,很少手动释放内存。不像 C/C++ 需要使用 free(),JS 引擎会帮我们自动回收内存。
但是,自动回收不等于不需要理解内存。很多前端的性能问题、页面卡顿、内存暴涨,其实都和垃圾回收机制有关。因此理解垃圾回收机制也尤为重要。
什么是垃圾回收?
垃圾回收(Garbage Collection,简称 GC):
自动识别'无法再访问'的对象,并释放其占用的内存。
核心问题只有一个:如何判断一个对象'没用了'?
那么基于这个定义,没有被引用的对象,就是垃圾。比如:
let obj = { name: "Tom" }
obj = null
当 obj = null 后:
- 原来的对象不再被引用
- 成为垃圾
- 等待回收
内存模型基础
JavaScript 的内存中主要可以分为两个部分:
- 栈(Stack)——存放基本类型和引用地址
- 基本类型(number、string、boolean、null、undefined、symbol、bigint)
- 函数的引用地址
- 堆(Heap)——存放对象
- 对象
- 数组
- 函数
- 闭包
- DOM 引用
例如:
let obj = { name: "Tom" }
obj存在栈中,因为这个是引用地址,{ name: "Tom" }存在堆中。
垃圾回收核心原理
那么怎么知道对象是否是垃圾?
标记清除算法
JS 引擎中有标记清除算法用来判断是否是垃圾,整个过程就是从 root 出发,看看是否能够被访问到。
Root 包括:
- 全局对象(浏览器中的
window,Node 中的global) - 当前调用栈中的变量
- 正在执行函数里的局部变量
- 闭包中被引用的变量
那么整个算法过程就分为标记阶段与清除阶段。
- 标记阶段
假设我们目前有以下这些对象:
Root ├── A
│ └── B
└── C
D
- A 被 Root 引用
- B 被 A 引用
- C 被 Root 引用
- D 没有人引用
整个过程就是先从 root 出发,先找到了 A 与 C,为 A 与 C 打上了'可达'标记。
继续向下遍历通过 A 找到了 B,给 B 打上了可达标记,整个过程就是一次深度优先搜索(DFS)或广度优先搜索(BFS)。
遍历结束后:

