Skip to content

实现的不同 ​

本节目标:理解“实现的不同”的核心思路,并能把它用于实际开发或面试表达。 底层实现上,CommonJS 和 ESModule (ESM) 的主要区别在于模块解析、加载、缓存和执行的机制不同。下面我们深入讲讲这两者的底层实现区别:

1. 加载机制CommonJS:运行时加载

  • require 是一个函数,在代码运行时被调用。
  • 加载模块时,Node 会同步读取文件内容,包装成函数执行(见下文)。
  • 缓存的是模块的 exports 对象。

底层流程: // require('foo') 背后的流程1. 解析模块路径2. 读取文件内容3. 包裹一层函数:(function(exports, require, module, __filename, __dirname) { ... })4. 执行这个函数,并传入 require 等参数5. 返回 module.exports

ESModule:编译时加载(静态)

  • import 是静态语法,在模块解析阶段就会确定依赖关系。
  • 加载是异步的,先分析依赖图,再执行代码。
  • ESModule 支持循环引用的实时绑定(live bindings)。

底层流程:

  1. 解析模块路径(支持 import map)2. 预扫描所有 import/export(构建依赖图)3. 确定模块之间依赖顺序4. 初始化模块(但不执行代码)5. 按依赖顺序执行模块

2. 包装机制CommonJS

  • Node 会将每个模块包裹成如下函数:(function (exports, require, module, __filename, __dirname) { // 模块源码});
  • 相当于每个文件是一个闭包,形成模块作用域。

ESModule

  • 不需要函数包裹,天然具有模块作用域。
  • 通过 V8 的Module Record 实现,每个模块是一个记录,存储导入导出等信息。

3. 缓存机制

特性CommonJSESModule
缓存位置require.cache内部模块记录表
缓存对象module.exportsModule Namespace Object
执行时机首次 require 就立即执行所有依赖扫描完后,再按顺序执行

4. 导出方式CommonJS

  • 值拷贝(不是 live binding):exports.a = 1;setTimeout(() => exports.a = 2, 1000);另一个模块 require 后,拿到的是 { a: 1 },不会变。

ESM

  • 实时绑定(live binding):export let a = 1;setTimeout(() => a = 2, 1000);import 这模块的另一个模块访问 a 会实时拿到最新值。

总结

项目CommonJSESModule
加载时机运行时加载编译阶段静态分析
执行机制同步,立即执行异步,按依赖图顺序执行
包装方式包裹成函数(Function Wrapper)原生模块作用域(Module Record)
导出机制值拷贝实时绑定(Live Binding)
缓存require.cache,缓存 exports模块记录,缓存 namespace 对象
支持循环引用支持但值固定支持并保证导出实时更新

如果你在研究 Node.js 内部实现或准备构建模块打包器(如 Webpack 插件),这些区别尤为重要。如果你想深入某一块(比如 module cache、循环引用处理、V8 的 Module 实现等),我可以继续扩展。 > 来自 <https://chatgpt.com/c/6805aef6-e7b4-8004-ab43-9a137f666985>

本站总访问 — 次