← 返回文章列表
JavaScript·53 次阅读

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 是语言级标准,加载分为独立的阶段:

  1. 解析/实例化:import 会被提前提升,先建立模块图和导出绑定(hoisted)
  2. 求值:再按依赖顺序执行代码

关键差异:

  • 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 条

还没有评论,来抢沙发~

发表评论

评论需审核后显示

© 2026 许冬博客 · 专注 JS 技术分享

·RSS 订阅·Powered by Nuxt 4