CommonJS(CJS)和 ES6 模块(ESM)是 JavaScript 中两套完全不同的模块规范 ,它们在设计理念、加载机制、运行行为上都有本质区别。
下面从 核心差异 → 运行机制 → 实战影响 系统讲清楚。
一、最核心的 10 个区别(速览表)
| 对比项 | CommonJS | ES6 Module |
|---|---|---|
| 诞生背景 | Node.js 社区规范 | ECMAScript 官方标准 |
| 加载方式 | 运行时同步加载 | 编译期静态分析 |
| 导入语法 | require() |
import |
| 导出语法 | module.exports |
export |
| 是否静态 | ❌ 动态 | ✅ 静态 |
| 是否支持 Tree Shaking | ❌ 不支持 | ✅ 支持 |
| 模块输出 | 值的拷贝 | **值的实时引用(活绑定)** |
| this 指向 | module.exports |
undefined |
| 循环依赖 | 返回"未完成副本" | 支持实时绑定 |
| 顶层 await | ❌ 不支持 | ✅ 支持(ES2022) |
二、加载机制的本质差异(最重要)
✅ CommonJS:运行时加载(动态)
const fs = require('fs'); // 执行到这里才加载
-
模块在 代码执行阶段 才加载
-
require是一个普通函数 -
可以写在条件判断里:
if (Math.random() > 0.5) {
require('./a');
} else {
require('./b');
}
✅ 灵活
❌ 无法静态分析
✅ ES Module:编译期静态解析
import fs from 'fs';
-
在 代码执行前 就被解析
-
import是语法,不是函数 -
不能动态写在 if 里
// ❌ 报错
if (true) {
import a from './a';
}
✅ 可被引擎提前分析
✅ 支持优化(Tree Shaking)
三、导出的值:拷贝 vs 实时绑定(非常关键)
1️⃣ CommonJS:值的拷贝
// a.js
let count = 1;
module.exports = { count };
// main.js
const { count } = require('./a');
count++;
console.log(count); // 2
❗ 导出的 count 是值的快照
2️⃣ ES Module:实时绑定(Live Binding)
// a.mjs
export let count = 1;
// main.mjs
import { count } from './a.mjs';
console.log(count); // 1
如果 a.mjs 里改了:
count++;
main.mjs 中 自动变化。
✅ 这是 ESM 最反直觉但最重要的特性
四、this 指向差异
// CommonJS
console.log(this === module.exports); // true
// ES Module
console.log(this); // undefined
✅ ESM 强制严格模式
✅ 没有 module 对象
五、循环依赖的处理方式
CommonJS:返回"半成品"
// a.js
exports.loaded = false;
const b = require('./b');
exports.loaded = true;
// b.js
exports.loaded = false;
const a = require('./a');
console.log(a.loaded); // false
❌ 可能读到未初始化的值
ES Module:支持实时绑定
// a.mjs
import { bLoaded } from './b.mjs';
export let aLoaded = false;
aLoaded = true;
// b.mjs
import { aLoaded } from './a.mjs';
export let bLoaded = false;
bLoaded = true;
✅ 双方都能拿到最终正确值
六、Tree Shaking 为什么只支持 ESM?
// utils.js
export function a() {}
export function b() {}
// main.js
import { a } from './utils.js';
✅ 打包工具能确定 b没被用
✅ 可以安全删除
而 CommonJS:
module.exports = { a, b };
❌ 无法确定哪些属性会被访问
❌ 不敢删
七、Node.js 中的现实差异
| 项目 | CommonJS | ES Module |
|---|---|---|
| 文件后缀 | .js |
.mjs或 "type": "module" |
| require | ✅ | ❌(默认) |
| import | ❌ | ✅ |
| __dirname | ✅ | ❌(需手动拼) |
| 顶层 await | ❌ | ✅ |
Node.js 中混合使用:
// ESM 中引入 CJS
import cjs from './cjs.cjs';
// CJS 中引入 ESM(Node 18+)
import('./esm.mjs').then(...);
八、一句话总结
**CommonJS 是为服务端设计的"运行时模块系统",
ES Module 是为通用 JavaScript 设计的"编译期模块标准"。**
✅ 新项目优先用 ES Module
✅ **库开发必须支持 ESM(才能 Tree Shaking)**
✅ Node 生态正在全面迁移到 ESM