模块记录是引擎私有元数据结构,不占js堆内存但通过导出绑定、导入请求和模块环境记录支配堆对象生命周期,其存在周期绑定模块图且结构在实例化后冻结。

模块执行上下文中的“模块记录”不是开发者能直接操作的对象,而是引擎内部用于管理模块状态的元数据结构。它不参与 JS 堆内存分配和垃圾回收,但通过绑定、导入和环境引用,深度影响模块内变量和对象的生命周期。
模块记录是引擎私有的元信息容器
它在模块解析完成、链接阶段之前由引擎(如 V8)创建,内存分配在引擎专用区(如 CodeSpace 或 ReadOnlySpace),而非 JS 堆。这意味着:
- 你无法用 console.dir 或堆快照工具观察到它本身;
- 它不计入 DevTools 中显示的 “JS Heap Size”,但会占用引擎内部内存;
- 它的存在周期严格绑定模块图(Module Graph):从加载开始,到整个图被卸载才释放。
模块记录通过三类引用支配 JS 堆行为
虽然自身不在堆中,但它持有关键引用,间接决定哪些 JS 对象必须长期驻留内存:
-
导出绑定(ExportEntries):记录
export const foo = {}这类声明,其中的{}是实际堆对象,只要模块记录存活,该对象就不可被 GC; - 导入请求(ImportEntries):保存对其他模块的依赖关系,形成有向图边;本模块未卸载,所导入模块的导出值也保持可达;
-
模块环境记录(Module Environment Record):对应模块顶层作用域,持有着所有顶层声明(包括
const obj = new Map())的初始值引用,使这些对象不会因“作用域结束”而释放。
实例化阶段的内存分配是延迟且按需的
模块记录在实例化时并不立即分配所有 JS 值,而是按需触发:
-
延迟初始化:像
export let data = new ArrayBuffer(100MB)这样的表达式,只在模块首次求值时执行,此时才在堆中真正分配内存; -
静态 import 在链接期建立完整引用链,而
dynamic import()返回 Promise,其模块记录及关联堆对象仅在 resolve 后创建; - 模块记录一旦创建,其结构不可变——导出名、导入项、环境引用关系在实例化后即冻结。
模块环境记录是执行上下文的关键组成部分
每个 ES 模块都有一个对应的模块环境记录,它是声明式环境记录的一种,专门承载模块顶层作用域的绑定。它与模块执行上下文强绑定,并将 [[OuterEnv]] 指向全局环境记录。这使得:
- 模块内
import的符号(如import { helper } from './lib')被作为只读绑定写入该环境; - 模块内
const/let/function声明的值,都存储在此环境中,并受模块记录统一管理; - 它不支持
this绑定或super状态,区别于函数环境记录。











