CommonJS 与 ESM:模块加载原理与差异
CommonJS 与 ESM:模块加载原理与差异
模块化是现代 JS 的地基。Node 世界有 CommonJS(CJS),语言标准有 ES Modules(ESM),理解二者差异对排查问题很重要。
CommonJS
每个文件就是一个模块,Node 执行时实际把文件内容包进函数:
(function (exports, require, module, __filename, __dirname) {
// 你的代码在这里,所以每个文件有独立作用域
})
核心特点:
require是运行时函数——可以写在条件里、动态拼路径,执行到这行才同步加载并执行模块module.exports导出的是一个值的拷贝(对象是引用);exports只是它的简写引用,直接exports = {}会断开关联- 模块首次加载后缓存,再次 require 拿到同一对象
- 同步加载,适合服务端本地文件
循环依赖时:CJS 返回当前未完成的 exports(执行到一半的状态),所以可能拿到空对象,要靠"延迟使用"规避。
ESM
ESM 是语言级标准,加载分为独立的阶段:
- 解析/实例化:import 会被提前提升,先建立模块图和导出绑定(hoisted)
- 求值:再按依赖顺序执行代码
关键差异:
import是静态的(顶层声明,不能放条件块),因此可以静态分析、Tree-shaking- 导出的是实时绑定(live binding)——引用的是同一个变量,导出方后续重新赋值,导入方看到的是最新值
- 循环依赖由模块图的求值顺序配合 live binding 处理,表现比 CJS 更可预期
- 天然支持异步(顶层 await),浏览器可原生使用
// ESM
import { name } from './a.js' // 即使写在后面,import 也会先处理
export const x = 1
互操作与识别
- package.json 中
"type": "module"决定 .js 按 ESM 解析;.cjs/.mjs 显式指定 - 打包工具(esbuild/Rollup)处理二者转换;Node 中 ESM 可通过 default 导入兼容 CJS,反之需要 createRequire
__dirname在 ESM 中不存在,用 import.meta.url 派生
对照速查
| CommonJS | ESM | |
|---|---|---|
| 时机 | 运行时同步加载 | 先解析链接再执行 |
| 导出 | 值/对象拷贝 | 实时绑定 |
| 动态 | require 随处写 | 动态需 import() |
| 优化 | 难 Tree-shaking | 可静态分析 |
| 现在建议 | 老代码 | 新项目首选 |
迁移建议:新包直接 ESM;发布 npm 库优先双格式输出(Vite library mode / tsup 很方便);理解加载阶段差异,循环依赖类疑难杂症基本都能解释。
看到这里了?如果这篇文章帮到过你
请我喝瓶可乐就好 🥤

评论0 条
还没有评论,来抢沙发~