CommonJS和ES6 Modules区别
CommonJS 和 ES Modules(ESM)的区别
CommonJS
和 ES Modules (ESM)
是 JavaScript 中两种主流的模块规范,分别用于不同场景(Node.js 与浏览器/现代 Node.js),核心区别体现在语法、加载机制、运行时行为等方面。
1. 语法差异(最直观的区别)
CommonJS
- 导出:使用
module.exports
或exports
- 导入:使用
require()
// 导出(math.js)
function add(a, b) { return a + b; }
module.exports = { add }; // 整体导出
// 或 exports.add = add; // 单个导出// 导入(index.js)
const math = require('./math.js');
console.log(math.add(1, 2)); // 3
ES Modules (ESM)
- 导出:使用
export
(命名导出)或export default
(默认导出) - 导入:使用
import
// 导出(math.js)
export function add(a, b) { return a + b; } // 命名导出
// 或 export default { add }; // 默认导出// 导入(index.js)
import { add } from './math.js'; // 对应命名导出
// 或 import math from './math.js'; // 对应默认导出
console.log(add(1, 2)); // 3
2. 加载机制:运行时加载 vs 编译时加载
CommonJS:运行时动态加载
- 模块加载发生在代码执行阶段(运行时),
require()
是一个函数,执行时才会加载模块。 - 支持动态导入(可在条件语句中加载模块):
- CommonJs是动态语法可以写在判断里
// 合法:根据条件动态加载
if (condition) {const math = require('./math.js');
}
- 加载的是模块的拷贝(值传递):导入后,模块内部的后续修改不会影响导入结果。
ESM:编译时静态加载
- 模块加载发生在代码解析阶段(编译时),
import
声明必须放在文件顶部,且不能在条件语句中使用(静态分析)。
- ES6 Module静态语法只能写在顶层,不支持直接在条件语句中写
import
,但可通过import()
函数实现动态加载(返回 Promise):
// 合法:动态导入(ES2020 新增)
if (condition) {import('./math.js').then(({ add }) => console.log(add(1, 2)));
}
- 加载的是模块的引用(引用传递):导入后,模块内部的修改会实时反映到导入处。
3. 模块依赖:动态依赖 vs 静态依赖
- CommonJS:依赖关系在运行时确定,模块路径可动态生成(如拼接字符串):
CommonJS 是运行时解析:
CommonJS 的模块加载是动态的,require()是一个运行时执行的函数,可以写在代码的任何位置(如条件语句、循环中)。模块的依赖关系只有在代码执行到require()语句时才会被解析和加载,无法在编译阶段(代码执行前)确定完整的依赖树。
const path = './math.js';
const math = require(path); // 合法
- ESM:依赖关系在编译时确定,模块路径必须是静态字符串(不能动态生成):
ES Modules 是编译时解析:
ES Modules 的 import 声明是静态的,必须写在模块的顶层(不能嵌套在条件语句中)。浏览器或 JS 引擎会在代码执行前(编译阶段)就解析所有 import 声明,构建完整的模块依赖关系图,这种静态分析能力让 ESM 支持树摇(tree-shaking)、类型检查等编译时优化。
const path = './math.js';
import { add } from path; // 报错!路径必须是静态的
4. 循环依赖处理
循环依赖指 A 依赖 B,B 同时依赖 A 的情况,两者处理方式不同:CommonJS
- 当模块循环引用时,
require()
会返回已执行部分的 exports(可能不完整)。 - 例:
// a.js
const b = require('./b.js');
console.log('a 中 b 的值:', b); // { b: undefined, getB: [Function] }
module.exports = { a: 1 };// b.js
const a = require('./a.js');
console.log('b 中 a 的值:', a); // {}(此时 a 尚未导出完成)
module.exports = { b: 2,getB: () => a.a // 后续可通过函数获取完整值
};
ESM
- 循环依赖时,模块会通过引用绑定保持关联,即使依赖未完全加载,也能获取到最终值。
- 例:
// a.js
import { b, getB } from './b.js';
console.log('a 中 b 的值:', b); // undefined(初始值)
export const a = 1;
console.log('a 中 getB():', getB()); // 1(获取到 a 的最终值)// b.js
import { a } from './a.js';
export let b = 2;
export const getB = () => a; // 引用 a 的绑定,最终会获取到 1
5. 环境支持
-
CommonJS:
- 主要用于 Node.js(默认模块系统),浏览器不原生支持(需通过 Webpack 等工具转译)。
- 模块文件扩展名可省略(Node.js 会自动补全
.js
、.json
等)。
- ESM:
- 浏览器原生支持(需在
<script type="module">
中使用)。 - Node.js 从 v12 开始支持(需将文件扩展名改为
.mjs
,或在package.json
中设置"type": "module"
)。
- 浏览器原生支持(需在
6. 顶层作用域
● CommonJS:
每个模块的顶层 this 指向 module.exports,且存在 module、exports、require 等内置变量。
● ES Modules:
顶层 this 为 undefined,没有 module、exports 等变量,只能通过 import/export 操作模块。
7. 加载方式
CommonJS:
○ 同步加载:模块加载会阻塞后续代码执行,适合服务器端(Node.js),因为文件存在于本地磁盘,加载速度快。
ES Modules:
○ 异步加载:模块加载不会阻塞代码执行,适合浏览器环境(网络请求可能延迟),且支持并行加载多个模块。
总结
● CommonJS 是 Node.js 的传统模块规范,适合服务器端,支持动态加载,但不支持 Tree-shaking。
● ESM 是 ES 标准模块规范,浏览器和现代 Node.js 均支持,静态加载支持 Tree-shaking,更适合前端工程化。