先甩结论 :
它们有三个重大差异。
- CommonJS 模块输出的是一个 值的拷贝 ,ES6 模块输出的是 值的引用。
- CommonJS 模块是 运行时 加载,ES6 模块是 编译时 输出接口。
- CommonJS 模块的
require()是 同步 加载模块,ES6 模块的import命令是 异步 加载,有一个独立的模块依赖的解析阶段。
一、核心定位与运行环境
- CommonJS(CJS) :Node.js 早期的模块化规范,为服务端设计(同步加载),也被 Browserify 等工具适配到浏览器端。
- ES6 模块(ESM) :ES6 官方定义的模块化规范,适配浏览器 + 服务端,是前端模块化的标准方案,Node.js v14.13+ 也全面支持。
二、差异对比表
| 对比维度 | CommonJS 模块 | ES6 模块 |
|---|---|---|
| 加载时机 | 运行时加载(动态):模块代码在 require 执行时才加载、执行 |
编译时加载(静态):编译阶段就确定模块依赖关系,提前解析导入导出 |
| 导入导出语法 | 导出:module.exports / exports;导入:require() |
导出:export / export default;导入:import / import() |
| 值的绑定方式 | 赋值传递:导入的是值的拷贝,模块内部值变化不影响导入值 | 引用传递:导入的是值的实时引用,模块内部值变化会同步到导入端 |
| 执行机制 | 模块代码同步执行,加载完成后才继续执行后续代码 | 模块代码异步执行(浏览器端),Node.js 中可同步 / 异步 |
| 顶层 this 指向 | 指向当前模块的 module.exports 对象 |
指向 undefined |
| 是否支持按需导入 | 不支持(静态分析无法识别 require 动态路径) |
支持(import() 动态导入、import { a } from 'xxx' 按需导入) |
| 循环依赖处理 | 加载时生成未完成的模块对象,后续补全(可能拿到空对象) | 编译时确定依赖关系,通过 "暂时性死区" 处理,更稳定 |
| 文件后缀 | 默认 .js,Node.js 中可省略后缀 |
浏览器端需 .mjs 或配置 type="module";Node.js 需 .mjs 或 package.json 设 "type": "module" |
三、关键差异详解(附代码示例)
1. 语法差异(最直观)
CommonJS 示例:
javascript
// 导出(module.js)
// 方式1:整体导出
module.exports = {
name: 'CommonJS',
sayHi: () => console.log('hi')
};
// 方式2:单个导出
exports.age = 18; // 等价于 module.exports.age = 18
// 导入(index.js)
const mod = require('./module.js');
console.log(mod.name); // CommonJS
console.log(mod.age); // 18
ES6 模块示例:
javascript
// 导出(module.js)
// 方式1:命名导出
export const name = 'ES6 Module';
export const age = 18;
// 方式2:默认导出
export default {
sayHi: () => console.log('hi')
};
// 导入(index.js)
// 命名导入 + 默认导入
import { name, age } from './module.js';
import defaultMod from './module.js';
console.log(name); // ES6 Module
defaultMod.sayHi(); // hi
2. 值绑定方式差异(核心区别)
CommonJS(值拷贝) :
javascript
// module.js(CJS)
let count = 0;
setTimeout(() => count = 10, 1000);
module.exports = { count };
// index.js(CJS)
const { count } = require('./module.js');
console.log(count); // 0(初始值拷贝)
setTimeout(() => console.log(count), 1000); // 仍为 0(拷贝值不更新)
ES6 模块(引用传递) :
javascript
// module.js(ESM)
export let count = 0;
setTimeout(() => count = 10, 1000);
// index.js(ESM)
import { count } from './module.js';
console.log(count); // 0(初始值)
setTimeout(() => console.log(count), 1000); // 10(实时引用更新)
3. 加载时机差异
- CommonJS:
require('./a.js')执行时才会读取a.js文件、执行代码、生成导出对象,支持动态路径(如require('./' + fileName))。 - ES6 模块:
import必须写在顶层(不能在 if 里),编译阶段就确定所有导入,不支持动态路径(需用import('./a.js')动态导入,返回 Promise)。
四、实战注意事项
-
Node.js 中混用两种模块:
- 后缀
.cjs强制为 CommonJS,.mjs强制为 ES6 模块; package.json中设置"type": "module",则.js文件默认是 ES6 模块;设为"commonjs"则默认是 CJS。
- 后缀
-
浏览器端使用 ES6 模块:
xml<!-- 必须加 type="module" --> <script type="module" src="./index.js"></script> -
循环依赖处理:ES6 模块的循环依赖更可靠,CommonJS 需注意模块加载顺序,避免拿到空对象。
总结
- 核心区别:CJS 是运行时动态加载(值拷贝),ESM 是编译时静态加载(值引用);
- 语法区别 :CJS 用
require/module.exports,ESM 用import/export; - 使用场景:Node.js 老项目多用 CJS,现代前端 / 新版 Node.js 优先用 ESM(标准化、支持按需加载)。