ES Module(ESM,ES6 模塊系統)和 CommonJS 是 JavaScript 中兩種主流的模塊規范,分別用于現代前端和 Node.js 環境(早期),它們在語法、加載機制、特性等方面有顯著區別。以下是詳細對比:
一、語法差異
1. 導出(Export)
ES Module:
使用 export 關鍵字,支持命名導出和默認導出,可在同一模塊中混合使用。
運行
// 命名導出(多個)
export const name = 'foo';
export function func() {};// 默認導出(一個模塊只能有一個)
export default { id: 1 };
CommonJS:
使用 module.exports 或 exports 導出,本質是導出一個對象(默認導出)。
運行
// 導出對象
module.exports = {name: 'foo',func: function() {}
};// 也可單獨賦值(通過 exports 引用 module.exports)
exports.name = 'foo';
exports.func = function() {};
2. 導入(Import)
ES Module:
使用 import 關鍵字,支持導入命名成員、默認成員或整體導入。
運行
// 導入命名成員
import { name, func } from './module.js';// 導入默認成員(可自定義名稱)
import obj from './module.js';// 整體導入(命名空間對象)
import * as mod from './module.js';
CommonJS:
使用 require() 函數導入,返回模塊導出的對象。
運行
// 整體導入
const mod = require('./module.js');
console.log(mod.name); // 訪問導出的成員// 解構導入(模擬命名導入)
const { name, func } = require('./module.js');
二、加載機制
1. 加載時機
ES Module:
- 靜態加載:導入和導出語句在代碼解析階段(編譯時)執行,而非運行時。
- 導入語句只能放在模塊頂層(不能在 if、函數等代碼塊中),瀏覽器 / 引擎可提前分析模塊依賴,實現樹搖(Tree Shaking)(刪除未使用的代碼)。
CommonJS:
- 動態加載:require() 在運行時執行,可根據條件動態導入(如在 if 語句中調用 require())。
- 無法在編譯時確定依賴關系,不支持樹搖。
2. 模塊加載方式
ES Module:
- 異步加載:在瀏覽器中,ESM 通過
運行
// module.js
export let count = 0;
export function increment() { count++; }// main.js
import { count, increment } from './module.js';
increment();
console.log(count); // 輸出 1(同步更新)
CommonJS:
- 同步加載:在 Node.js 中,require() 是同步加載,適合服務端(文件讀取快),不適合瀏覽器(會阻塞渲染)。
- 值的拷貝:導入的是模塊導出值的 “拷貝”,模塊內部后續修改不會影響導入方。
運行
// module.js
let count = 0;
module.exports = {count,increment() { count++; }
};// main.js
const mod = require('./module.js');
mod.increment();
console.log(mod.count); // 輸出 0(拷貝未更新)
三、模塊標識與路徑
ES Module:
- 必須使用完整路徑(包括文件擴展名,如 .js、.mjs),或通過配置(如 package.json 的 type: “module”)省略。
- 支持絕對路徑、相對路徑和 URL(如
import 'https://cdn.example.com/module.js'
)。
運行
import './utils.js'; // 必須帶 .js 擴展名(Node.js 中需配置)
CommonJS:
- 可省略文件擴展名(.js、.json 等會被自動補全),支持查找 node_modules 中的模塊。
運行
require('./utils'); // 自動補全為 ./utils.js
require('lodash'); // 從 node_modules 中查找
四、循環依賴處理
ES Module:
- 遇到循環依賴(A 依賴 B,B 依賴 A)時,會返回 “未完成的模塊實例”(部分導出已可用),后續代碼執行時補充完整。
運行
// a.js
import { b } from './b.js';
export const a = 1;
console.log('a 中 b 的值:', b); // 輸出 undefined(此時 b 尚未導出)// b.js
import { a } from './a.js';
export const b = 2;
console.log('b 中 a 的值:', a); // 輸出 1(a 已導出)
CommonJS:
- 循環依賴時,會緩存已執行的部分模塊,返回 “緩存的導出對象”(可能不完整)。
運行
// a.js
const { b } = require('./b.js');
exports.a = 1;
console.log('a 中 b 的值:', b); // 輸出 undefined(b 尚未導出)// b.js
const { a } = require('./a.js');
exports.b = 2;
console.log('b 中 a 的值:', a); // 輸出 undefined(a 尚未導出)
五、適用環境
ES Module:
- 現代瀏覽器(原生支持,需·
<script type="module">
)。
Node.js 14.3+(需文件后綴為 .mjs 或 package.json 中設置 “type”: “module”)。 - 前端工程化工具(Webpack、Vite 等)默認支持,是前端模塊化的主流方案。
CommonJS:
- 主要用于 Node.js 環境(默認模塊系統),早期前端通過 Browserify、Webpack 等工具轉換后使用。
- 目前 Node.js 仍廣泛兼容,但新項目更推薦 ESM。
六、核心區別總結
七、如何選擇?
- 前端項目:優先使用 ES Module,配合工程化工具實現樹搖和優化。
- Node.js 項目:新項目推薦 ES Module(“type”: “module”),舊項目兼容 CommonJS。
- 需動態加載模塊(如根據條件導入):可在 ESM 中使用 import() 函數(返回 Promise,支持動態加載),兼具靜態分析和動態能力。