ECMAScript Modules(ESM)和 CommonJS(CJS)是 JavaScript 中两种主要的模块系统,它们各有特点和差异。
1. 语法
-
ESM(ECMAScript Modules):
- 导入:使用
import
语法import { foo } from "./module.js"; import * as bar from "./module.js"; import defaultExport from "./module.js";
- 导出:使用
export
语法export const foo = 42; export function bar() { ... } export default function() { ... }
- 导入:使用
-
CJS(CommonJS):
- 导入:使用
require
语法const foo = require("./module.js").foo; const bar = require("./module.js");
- 导出:使用
module.exports
和exports
module.exports = { foo: 42, bar: function() { ... } }; // 或者 exports.foo = 42; exports.bar = function() { ... };
- 导入:使用
2. 模块加载方式
- ESM:
- 静态:模块加载在编译时确定,可以进行静态分析,支持树摇优化(tree-shaking)。
- 异步:支持异步加载(动态导入),可以使用
import()
来按需加载模块。
- CJS:
- 动态:模块加载在运行时进行,
require
可以在任何地方调用,包括条件语句中。 - 同步:
require
是同步的,所有模块在程序开始时加载。
- 动态:模块加载在运行时进行,
3. 文件扩展名
- ESM:
- 通常使用
.js
文件扩展名,默认情况下会使用ESM
语法,但也可以使用.mjs
扩展名明确标识为 ESM 模块。
- 通常使用
- CJS:
- 通常使用
.js
文件扩展名,模块系统默认为CJS
。使用.cjs
扩展名可以明确标识为 CJS 模块。
- 通常使用
4. 模块解析
- ESM:
- 相对路径:需要提供完整的路径(包括文件扩展名),例如
import { foo } from './module.js';
- 绝对路径:支持 URL 作为模块标识符。
- 相对路径:需要提供完整的路径(包括文件扩展名),例如
- CJS:
- 相对路径:可以省略文件扩展名,例如
const foo = require('./module');
,Node.js 会尝试自动解析.js
、.json
、.node
等扩展名。
- 相对路径:可以省略文件扩展名,例如
5. 模块缓存
- ESM:
- 模块加载结果会被缓存,后续的导入会从缓存中读取。
- CJS:
- 模块首次
require
时加载,后续的require
会使用缓存的结果。
- 模块首次
6. 默认导出
- ESM:
- 支持默认导出,可以导出一个默认值和多个命名值。
- CJS:
module.exports
可以导出一个对象或函数,不能直接导出多个命名值。
7. this
绑定
- ESM:
- 在模块代码中,
this
是undefined
。模块代码不在函数上下文中运行,而是在模块上下文中运行。
- 在模块代码中,
- CJS:
- 在模块代码中,
this
指向module.exports
,即模块的导出对象。
- 在模块代码中,
示例对比
ESM 示例:
// module.js
export const foo = 42;
export default function() { ... }
// main.js
import { foo } from './module.js';
import defaultExport from './module.js';
CJS 示例:
// module.js
const foo = 42;
module.exports = function() { ... };
module.exports.foo = foo;
// main.js
const module = require('./module.js');
const foo = module.foo;
const defaultExport = module;