核心区别概览
| 特性 | ES6模块(ESM) | CommonJS(CJS) |
|---|
| 标准来源 | ECMAScript标准 | Node.js社区规范 |
| 设计目标 | 浏览器 + 服务器 | 服务器端(Node.js) |
| 语法 | import/export | require()/module.exports |
| 加载时机 | 编译时静态解析 | 运行时动态加载 |
| 加载方式 | 异步(浏览器) | 同步(Node.js) |
| 值引用 | 动态绑定(引用) | 值拷贝 |
| 文件扩展名 | .mjs或设置type: "module" | .cjs或.js |
详细区别
1. 语法差异
ES6模块(ESM)
export const name = 'Alice';
export function hello() {};
export default someFunction;
import { name, hello } from './module.js';
import someFunction from './module.js';
import * as Module from './module.js';
CommonJS(CJS)
module.exports = someFunction;
exports.name = 'Alice';
const someFunction = require('./module');
const { name } = require('./module');
2. 加载时机
| ES6模块 | CommonJS |
|---|
| 解析时机 | 编译时静态分析 | 运行时动态加载 |
| 特点 | 支持静态分析,利于tree-shaking | 灵活,支持条件加载 |
| 示例 | javascript<br>// 正确<br>import { foo } from './module';<br><br>// 错误(语法错误)<br>if (condition) {<br> import { bar } from './module';<br>}<br> | javascript<br>// 正确<br>let module;<br>if (condition) {<br> module = require('./moduleA');<br>} else {<br> module = require('./moduleB');<br>}<br> |
3. 值传递机制
ES6模块:动态绑定(引用)
export let count = 0;
export function increment() {
count++;
}
import { count, increment } from './counter.mjs';
console.log(count);
increment();
console.log(count);
CommonJS:值拷贝
let count = 0;
module.exports = {
count,
increment: function() {
count++;
}
};
const counter = require('./counter.js');
console.log(counter.count);
counter.increment();
console.log(counter.count);
4. 循环依赖处理
ES6模块
import { b } from './b.mjs';
export const a = 'a';
import { a } from './a.mjs';
console.log(a);
export const b = 'b';
CommonJS
exports.a = 'a';
const b = require('./b.js');
console.log(b.b);
exports.b = 'b';
const a = require('./a.js');
console.log(a.a);
5. 运行环境支持
| 环境 | ES6模块 | CommonJS |
|---|
| 现代浏览器 | ✅ 原生支持(需<script type="module">) | ❌ 不支持 |
| Node.js | ✅ v13.2.0+(需.mjs扩展名或package.json设置) | ✅ 原生支持 |
| 旧版浏览器 | ❌ 需打包工具转换 | ❌ 需打包工具转换 |
6. 顶级this值
console.log(this);
console.log(this);
7. 互操作性
ESM中导入CJS
import cjsModule from './commonjs-module.cjs';
CJS中导入ESM(Node.js)
async function main() {
const esmModule = await import('./es-module.mjs');
}
main();
实际应用建议
何时使用哪种?
| 场景 | 推荐 |
|---|
| 前端项目(React/Vue) | ✅ ES6模块(通过Webpack等打包工具) |
| Node.js新项目 | ✅ ES6模块(.mjs或设置"type": "module") |
| Node.js旧项目/库 | ✅ CommonJS(兼容性考虑) |
| 跨平台库开发 | 🔄 建议提供双模式或打包为UMD |
配置示例
package.json 配置:
{
"name": "my-package",
"type": "module",
"exports": {
".": {
"import": "./index.mjs",
"require": "./index.cjs"
}
}
}
迁移注意事项
- 文件扩展名:
.mjs → ES6模块
.cjs → CommonJS模块
- __dirname和__filename:
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
- 包导入:
const path = require('path');
import path from 'path';
总结
| 方面 | 关键区别 |
|---|
| 发展方向 | ES6模块是未来标准,CommonJS是历史标准 |
| 性能 | ES6模块支持tree-shaking,打包体积更小 |
| 灵活性 | CommonJS动态加载更灵活 |
| 生态系统 | 前端生态已全面转向ESM,Node.js正在过渡 |
| 学习成本 | ES6模块语法更现代、一致 |
现代开发建议:新项目优先使用ES6模块,老项目根据需求逐步迁移。对于库开发,考虑同时支持两种模块系统以最大化兼容性。