ES Module 与 CommonJS 的核心差异
1. 语法与设计理念
CommonJS (CJS)
- 语法:
require()导入,module.exports或exports导出。 - 设计目标:服务端模块化(如 Node.js),同步加载,运行时解析依赖。
1
2
3
4// 导出
module.exports = { key: 'value' };
// 导入
const obj = require('./module');
- 语法:
ES Module (ESM)
- 语法:
import导入,export导出。 - 设计目标:浏览器与跨平台,静态编译(编译时分析依赖),支持异步加载(如
import())。1
2
3
4// 导出
export const key = 'value';
// 导入
import { key } from './module.mjs';
- 语法:
2. 加载机制与值传递
| 特性 | CommonJS | ES Module |
|---|---|---|
| 加载时机 | 运行时同步加载 | 编译时解析,运行时执行 |
| 值传递 | 导出值的拷贝 | 导出值的引用(动态绑定) |
| 循环依赖处理 | 通过缓存避免死循环,可能输出未完成状态的值 | 引用未初始化变量可能为 undefined(静态分析报错优先) |
| 作用域 | 模块级作用域(通过函数包裹) | 原生词法作用域 |
3. 异步支持与适用场景
- CJS:仅同步加载,适用于服务端(文件读取快),浏览器端易阻塞渲染。
- ESM:支持异步加载(
import()),适用于浏览器与现代 Node.js,支持按需加载和 Tree Shaking。
4. 其他关键差异
- 顶层
this指向:CJS 指向当前模块,ESM 指向undefined。 - 动态表达式:CJS 的
require()可动态调用(如require(path)),ESM 的import路径需静态字符串(动态路径需import())。
互相加载的方式
1. ESM 加载 CommonJS
- 默认行为:ESM 的
import会将 CJS 的module.exports视为default导出。1
2
3
4
5// CJS 模块 (math.cjs)
module.exports = { add: (a, b) => a + b };
// ESM 导入
import math from './math.cjs'; // math = { add: [Function] } - 命名导入的限制:
需通过解构从default中提取,直接命名导入会报错:1
2
3
4
5
6// 错误!
import { add } from './math.cjs';
// 正确:解构 default
import cjsModule from './math.cjs';
const { add } = cjsModule;
2. CommonJS 加载 ESM
- 问题:CJS 的
require()是同步的,而 ESM 需异步加载。 - 解决方案:使用动态
import()(返回 Promise):1
2
3
4
5// CJS 中加载 ESM
(async () => {
const esmModule = await import('./module.mjs');
console.log(esmModule.key);
})();
3. 兼容方案
- 统一封装:若需同时支持两种规范,可创建适配层:
1
2
3// 适配 CJS 模块为 ESM (wrapper.mjs)
import cjsModule from './legacy.cjs';
export const { propA, propB } = cjsModule;
与 AMD 的关系及差异
AMD 的角色
- 定位:浏览器端异步加载的早期方案(如 RequireJS),解决 CJS 同步加载阻塞问题。
- 语法:
define定义模块,require异步加载:1
2
3define(['dep1', 'dep2'], (dep1, dep2) => {
return { key: dep1.value + dep2.value };
}); - 对比 ESM/CJS:
特性 AMD ESM CommonJS 加载方式 异步 静态/支持异步 同步 适用环境 浏览器 浏览器/Node.js Node.js 依赖处理 依赖前置 静态分析 运行时解析
演进关系
AMD 是 ESM 成熟前浏览器的过渡方案,ESM 成为标准后逐渐被取代,但动态加载思想(如 import())吸收了其优点。
总结:如何选择?
- Node.js 环境:传统库用 CommonJS,新项目首选 ESM(设置
"type": "module")。 - 浏览器环境:ES Module(原生支持 + 异步能力)。
- 历史项目:AMD 仅需在维护旧浏览器代码时考虑。
通过
import/require混用和动态加载,可实现渐进迁移,但需注意值传递和加载时序的差异。