Skip to content
字数
1778 字
阅读时间
8 分钟

ES Modules (ESM) 与 CommonJS (CJS) 的区别、优劣及设计差异

一、核心区别

ES Modules(ES6 引入的官方模块系统)和 CommonJS(Node.js 采用的模块系统)是 JavaScript 中两种主流的模块化方案,核心区别如下:

维度ES Modules (ESM)CommonJS (CJS)
语法使用 import 导入、export 导出使用 require() 导入、module.exports 导出
加载时机静态加载(编译时解析模块依赖)动态加载(运行时解析模块依赖)
导入性质导入的是绑定(binding)(只读引用)导入的是值的拷贝(原始类型拷贝,对象浅拷贝)
模块标识支持相对路径、绝对路径、URL(如 http://支持相对路径、绝对路径、内置模块、npm 包
异步性支持异步加载(如 import() 动态导入)同步加载(阻塞执行)
顶层 thisundefined指向当前模块 module.exports
循环依赖处理基于“引用”,未执行的模块会返回空对象占位基于“值拷贝”,未执行完的模块返回已执行部分

二、优劣对比

1. ES Modules (ESM)

优势

  • 静态分析支持:编译时即可确定模块依赖关系,支持 tree-shaking(消除未使用代码)、类型检查(如 TypeScript)、IDE 智能提示等优化。
  • 只读引用:导入的变量是原模块的绑定,原模块更新后导入方会同步感知(更符合“模块”的设计理念)。
  • 异步加载:支持 import() 动态导入(返回 Promise),适合浏览器端按需加载(如路由懒加载)。
  • 标准化:ECMAScript 官方标准,浏览器和 Node.js 均已支持(Node.js 需通过 .mjspackage.json "type": "module" 启用)。

劣势

  • 兼容性限制:旧浏览器(如 IE)不支持,需通过 Babel 等工具转译。
  • 静态语法限制import 语句必须位于顶层(不能在条件语句中),模块路径不能是动态表达式(如 import('./' + path),需用 import() 替代)。
2. CommonJS (CJS)

优势

  • 动态灵活性require() 可以在任意位置调用(如条件语句、函数内),路径支持动态表达式(如 require('./' + filename))。
  • Node.js 生态默认支持:大量 npm 包基于 CJS 编写,无需额外配置即可运行。
  • 同步加载简单直接:适合 Node.js 服务端场景(文件读取快,同步加载逻辑更直观)。

劣势

  • 不支持静态优化:运行时才能解析依赖,无法实现 tree-shaking,打包体积较大。
  • 值拷贝问题:导入的是值的拷贝,原模块更新后导入方不会感知(对象类型虽为浅拷贝,但修改引用需手动重新导入)。
  • 浏览器不原生支持:需通过 Webpack、Browserify 等工具打包后才能在浏览器运行。

三、设计理念:为何 ESM 静态化,CJS 动态化?

两种模块系统的设计差异源于其目标场景和需求的不同:

1. ESM 静态化的原因

ESM 诞生的核心目标是为 JavaScript 提供一个标准化、高效的模块系统,兼顾浏览器和服务器端,尤其优化浏览器端的加载和解析效率。

  • 静态分析需求:浏览器端代码需要通过网络加载,静态化允许在编译时(甚至构建阶段)确定依赖关系,从而实现:
    • 依赖预加载(提前请求所需模块,减少加载时间);
    • tree-shaking(删除未使用的代码,减小包体积);
    • 静态类型检查(如 TypeScript 依赖模块结构的静态确定)。
  • 模块化规范性:静态语法(import 必须顶层声明)强制模块依赖关系清晰可见,避免动态加载导致的代码逻辑混乱,提升可维护性。
  • 与 ES 语言特性协同:ESM 设计时结合了块级作用域、let/const 等新特性,静态绑定(只读引用)更符合变量的声明语义。
2. CJS 动态化的原因

CJS 是 Node.js 为了解决服务端模块化需求而设计的,核心目标是灵活处理文件系统中的模块加载

  • 服务端场景特性:Node.js 模块主要从本地文件系统加载,同步加载成本低,且需要应对复杂的动态场景:
    • 动态路径加载(如根据配置文件动态加载不同模块);
    • 条件加载(如根据环境变量 process.env.NODE_ENV 加载开发/生产模块)。
  • 快速迭代需求:Node.js 早期追求简单实用,动态化设计实现起来更直观,无需复杂的编译时分析,适合快速开发服务端工具和应用。
  • CommonJS 规范历史:CJS 源于社区早期的模块化探索(如 require.js 之前的方案),动态化是当时对“灵活性”的优先选择。

四、总结

  • ESM 是标准化的静态模块系统,适合需要优化、Tree-shaking、跨平台(浏览器/Node.js)的场景,牺牲了部分动态灵活性换取性能和规范性。
  • CJS 是动态模块系统,适合 Node.js 服务端场景,灵活性高但无法支持静态优化,主要依赖运行时解析。

随着 ESM 被 Node.js 逐步支持(自 v12 起稳定),生态正逐步向 ESM 迁移,但 CJS 因历史积累仍会长期存在,二者的互操作(如 ESM 导入 CJS 模块)也是当前的重要议题。

其他模块

  • AMD 和 CMD 都是浏览器端的 JS 模块化规范,分别由 require.js 和 sea.js 实现
  • CommonJS 是服务器端的 js 模块化规范,由 NodeJS 实现
  • UMD 是希望解决跨平台的解决方案 (UMD 是 AMD 和 Common JS 的糅合)

AMD - requireJS

The Asynchronous Module Definition (AMD) 规范

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出

CMD - seaJS

同理

区别

1.CMD 推崇依赖就近,AMD 推崇依赖前置
2.对于依赖的模块,AMD 是提前执行,CMD 是延迟执行

参考

ES6 系列之模块加载方案 - 掘金

Common JS、AMD、CMD 和 UMD 的区别 - 掘金

从 Nodejs 如何解决模块循环依赖问题来一点关于模块的发散思考 - 掘金

贡献者

The avatar of contributor named as jiechen jiechen
The avatar of contributor named as chenjie chenjie

页面历史

撰写