ESM(ECMAScript Modules)和 CommonJS 是两种 JavaScript 模块系统,它们用于在不同文件间组织和共享代码。两者有显著的区别,并且各自有优缺点。
一、ESM(ECMAScript Modules)
ESM 是 JavaScript 的原生模块系统,从 ECMAScript 6 (ES6) 开始引入。它在现代浏览器和 Node.js 中都受到支持。
1. 语法
导出: 使用
export
来导出模块中的变量、函数、类等。js// 导出单个值 export const foo = 'bar'; // 导出多个值 const a = 1, b = 2; export { a, b }; // 默认导出 export default function () { console.log('default export'); }
导入: 使用
import
来导入模块的内容。js// 导入模块 import { a, b } from './module.js'; // 导入默认导出 import defaultFunction from './module.js';
2. 特点
- 静态分析:ESM 在编译阶段解析,因此它支持静态分析(如 Tree Shaking,移除未使用的代码),有利于打包优化。
- **作用域:**每个模块都有自己的独立作用域,导入的模块内容是只读的。
- 顶层
await
支持:ESM 支持在模块的顶层使用await
,这在处理异步代码时非常有用。 - 异步加载:浏览器环境中,ESM 支持按需异步加载模块,通过
<script type="module">
标签实现。
3. 优点
- 性能优化:由于支持静态分析,现代打包工具(如 Webpack、Rollup)可以对 ESM 进行 Tree Shaking,减少不必要的代码。
- 浏览器原生支持:无需打包工具,直接在现代浏览器中使用。
- 严格模式:ESM 模块默认使用严格模式(
use strict
),这能避免一些常见的 JavaScript 错误。
4. 缺点
- 兼容性问题:虽然 ESM 在现代浏览器和 Node.js 中广泛支持,但在较旧的环境中可能不支持,需要通过工具(如 Babel)进行转换。
- 异步加载:在 Node.js 中,ESM 的模块加载是异步的,相比于 CommonJS 的同步加载机制,这在某些场景下会带来额外的复杂性。
二、CommonJS
CommonJS 是最早为服务器端 JavaScript(如 Node.js)设计的模块系统,并成为 Node.js 的默认模块标准。
1. 语法
导出: 使用
module.exports
来导出模块中的内容。js// 导出单个值 module.exports = { foo: 'bar' }; // 导出多个值 exports.a = 1; exports.b = 2;
导入: 使用
require
来导入模块。jsconst module = require('./module.js'); const { a, b } = require('./module.js');
2. 特点
- 同步加载:CommonJS 模块是同步加载的,这对服务器端来说是合适的,因为文件系统操作是本地的、快速的。
- 单次加载、缓存机制:模块在首次加载时执行并缓存,后续的
require
调用会直接使用缓存结果,不会重复执行模块代码。 module.exports
和exports
:module.exports
是导出对象,而exports
是对module.exports
的引用。二者在导出单一内容时容易混淆。
3. 优点
- Node.js 的默认模块系统:CommonJS 是 Node.js 的原生模块格式,使用广泛且兼容性好。
- 简单的同步加载:模块同步加载,适用于服务器端的快速读取文件系统的场景。
4. 缺点
- 无法进行静态分析:由于 CommonJS 的模块系统是动态的,导入的内容只有在运行时才能确定,因此打包工具无法进行 Tree Shaking 等静态优化。
- 浏览器不支持:浏览器不能原生运行 CommonJS 模块,必须通过打包工具将其转换为 ESM 或其他格式。
- 模块之间依赖不明确:因为
require
是动态的,难以在编译阶段分析模块之间的依赖关系。
三、区别总结
特点 | ESM | CommonJS |
---|---|---|
引入方式 | import | require |
导出方式 | export / export default | module.exports / exports |
加载方式 | 静态、异步加载 | 动态、同步加载 |
是否支持静态分析 | 支持,编译时可分析依赖关系和导入内容 | 不支持,只能在运行时确定 |
模块作用域 | 独立作用域,导入的内容是只读的 | 独立作用域,但导入的内容可以修改 |
缓存机制 | 有,导入后缓存 | 有,首次加载后缓存,不会重复执行 |
顶层 await | 支持 | 不支持 |
适用场景 | 现代前端开发、浏览器和 Node.js 环境 | Node.js 服务端开发 |
四、选择建议
在 Node.js 中:
- 如果你的项目是现代的,并且可以使用 Node.js v12+,建议使用 ESM,因为它是未来的标准,支持更好的优化和异步功能。
- 如果你的项目依赖于较旧的模块系统或工具链,可以继续使用 CommonJS,尤其是当你需要与大量 CommonJS 模块集成时。
在前端开发中:
- ESM 是浏览器原生支持的模块系统,适合用于现代前端项目,并且与打包工具(如 Webpack、Rollup)结合时可以发挥更好的性能优化。