模块化方案对比:CommonJS、AMD、ES Module
比较 CommonJS、AMD、CMD 与 ES Module 的加载方式和导出机制
问题
CommonJS、AMD、CMD 与 ES Module 有什么区别?主要从动态/静态加载、值拷贝/引用两个维度分析。
解答
CommonJS
Node.js 采用的模块规范,同步加载,运行时确定依赖。
// math.js
let count = 0;
function add(a, b) {
count++;
return a + b;
}
module.exports = {
count,
add
};
// main.js
const math = require('./math'); // 同步加载,运行时执行
console.log(math.count); // 0
math.add(1, 2);
console.log(math.count); // 0 —— 值拷贝,不会变化
AMD (Asynchronous Module Definition)
RequireJS 实现的规范,异步加载,适合浏览器环境。
// 定义模块
define('math', [], function() {
let count = 0;
return {
add: function(a, b) {
count++;
return a + b;
},
getCount: function() {
return count;
}
};
});
// 使用模块
require(['math'], function(math) {
// 异步回调,依赖前置
console.log(math.add(1, 2));
});
CMD (Common Module Definition)
SeaJS 实现的规范,异步加载,依赖就近。
// 定义模块
define(function(require, exports, module) {
// 依赖就近,用到时再 require
const math = require('./math');
console.log(math.add(1, 2));
// 条件加载
if (needOther) {
const other = require('./other');
}
});
ES Module
ES6 原生模块系统,静态分析,编译时确定依赖。
// math.js
export let count = 0;
export function add(a, b) {
count++;
return a + b;
}
// main.js
import { count, add } from './math.js'; // 静态导入,编译时分析
console.log(count); // 0
add(1, 2);
console.log(count); // 1 —— 引用绑定,实时反映变化
值拷贝 vs 引用绑定
// === CommonJS:值拷贝 ===
// counter.js
let count = 0;
module.exports = {
count,
increment() { count++; }
};
// main.js
const counter = require('./counter');
console.log(counter.count); // 0
counter.increment();
console.log(counter.count); // 0 —— 还是 0,因为是拷贝
// === ES Module:引用绑定 ===
// counter.js
export let count = 0;
export function increment() { count++; }
// main.js
import { count, increment } from './counter.js';
console.log(count); // 0
increment();
console.log(count); // 1 —— 变成 1,因为是引用
动态加载 vs 静态加载
// CommonJS:动态加载,可以条件引入
if (condition) {
const moduleA = require('./a');
} else {
const moduleB = require('./b');
}
// ES Module:静态加载,import 必须在顶层
import { a } from './a.js'; // 必须在顶层,不能在 if 里
// ES Module 动态导入(ES2020)
if (condition) {
const module = await import('./a.js'); // 返回 Promise
}
对比表格
| 特性 | CommonJS | AMD | CMD | ES Module |
|---|---|---|---|---|
| 加载方式 | 同步 | 异步 | 异步 | 编译时静态分析 |
| 运行环境 | Node.js | 浏览器 | 浏览器 | 通用 |
| 导出机制 | 值拷贝 | 值拷贝 | 值拷贝 | 引用绑定 |
| 依赖声明 | 运行时 | 前置声明 | 就近声明 | 静态声明 |
| Tree Shaking | 不支持 | 不支持 | 不支持 | 支持 |
关键点
- CommonJS 同步加载:
require运行时执行,导出值的拷贝,适合服务端 - AMD 异步前置:依赖前置声明,回调中使用,RequireJS 实现
- CMD 异步就近:依赖就近书写,用时再加载,SeaJS 实现
- ES Module 静态分析:编译时确定依赖,导出值的引用绑定,支持 Tree Shaking
- 值拷贝 vs 引用:CommonJS 导出后修改原值不影响已导入的值;ES Module 导出的是实时绑定,原值变化会同步
目录