深入淺出(chu) ES Module
概述
在 JavaScript 模塊化發展歷程中,為解決全局變量污染,代碼依賴管理等(deng)問題,先后出現了 CommonJS(CJS)、AMD、CMD、UMD、ES6 Module(ESM)五大主流方(fang)案(an)。不同方(fang)案(an)因設計目(mu)標、運行環境(瀏覽器 / Node)的差異,形成了各自的語法特性(xing)與生(sheng)態(tai)定(ding)位。
其中ES6 Module是 ES6 官方定義的標準化模塊化方案,旨在統一瀏覽器與服務(wu)器端(duan)的模塊化語法,解決傳統模塊化方案(如 CommonJS、AMD)的碎片(pian)化問題。
核心特性
- 靜態化設計:模塊的導入(import)與導出(export)聲明必須在模塊頂層,且導入導出的模塊標識符(如文件路徑)需是靜態字符串,不能動態拼接(如 ·import('./' + path)· 是不允許的)。這一特性讓 JavaScript 引擎在編譯階段就能分析模塊依賴關系,實現 Tree-Shaking(搖樹優化,剔除未使用的代碼)。
- 獨立模塊作用域:每個 ESM 模塊都是一個獨立的作用域,模塊內聲明的變量、函數、類默認不對外暴露,需通過export顯式導出后,其他模塊才能通過import導入使用,這樣可以避免全局變量污染。
- 值引用特性:ESM 導入的模塊成員是 “值的引用”(而非 CommonJS 的值的拷貝),若導出模塊修改了導出的變量(如導出一個 let 聲明的變量并后續修改),導入模塊會同步感知到變化(需注意:基礎類型值若用const聲明,因不可修改,不會有此特性)。
- 異步加載:在瀏覽器環境中,ESM 默認通過
<script type="module">標簽異步加載(相當于給<script>加了defer屬性),不會阻塞 HTML 解析,且模塊加載完成后會按依賴順序執行。
基礎語法
導出(export)
- 命名導出:導出多個獨立的模塊成員,導入時需用相同的名稱接收(可通過as重命名);
- 默認導出:每個模塊最多只能有一個默認導出,導入時可自定義接收名稱(無需加大括號)。
// 模塊A:moduleA.js
// 1. 命名導出(方式1:聲明時導出)
export const name = 'ES6 Module';
export function add(a, b) {
return a + b;
}
// 2. 命名導出(方式2:集中導出)
const age = 6;
const multiply = (a, b) => a * b;
export { age, multiply as multiplyFn }; // as重命名導出
// 3. 默認導出(方式1:直接導出)
export default class ModuleClass {
constructor() {
this.version = '1.0.0';
}
}
// 4. 默認導出(方式2:先聲明后導出)
const defaultObj = { type: 'module' };
export default defaultObj;
導入語法(import)
- 導入命名成員:需用大括號包裹導入的成員名稱,與導出名稱一致(可通過as重命名);
- 導入默認成員:無需大括號,可自定義接收名稱;
- 整體導入:用
* as 模塊名導入整個模塊的所有導出成員,訪問時需通過模塊名.成員名的方式; - 導入執行:若導入模塊僅需執行其代碼(如執行初始化邏輯,無需使用其導出成員),可直接使用
import './moduleA.js'。'
// 模塊B:moduleB.js
// 1. 導入命名成員(原樣接收)
import { name, add } from './moduleA.js';
console.log(name); // 輸出:ES6 Module
console.log(add(2, 3)); // 輸出:5
// 2. 導入命名成員(重命名接收)
import { age, multiplyFn as multiply } from './moduleA.js';
console.log(age); // 輸出:6
console.log(multiply(2, 3)); // 輸出:6
// 3. 導入默認成員(自定義名稱)
import ModuleClass from './moduleA.js'; // 無需大括號
const instance = new ModuleClass();
console.log(instance.version); // 輸出:1.0.0
// 4. 混合導入(命名成員+默認成員)
import ModuleClass, { name as moduleName } from './moduleA.js';
console.log(moduleName); // 輸出:ES6 Module
// 5. 整體導入
import * as ModuleA from './moduleA.js';
console.log(ModuleA.name); // 輸出:ES6 Module
console.log(ModuleA.add(2, 3)); // 輸出:5
console.log(new ModuleA.default().version); // 輸出:1.0.0(默認成員需通過.default訪問)
// 6. 導入執行
import './moduleA.js'; // 僅執行moduleA.js的代碼,不使用其導出成員
模塊方案對比
CJS(CommonJS)
Node.js 原生支持的模塊化方案,面向服務端,采用運行時動態加載,通過(guo)require導(dao)(dao)入、module.exports導(dao)(dao)出;
- 設計目標
? 解決 Node.js 服務端模塊依賴管理問題,避免全局變量污染;
? 基于文件即模塊理念,每個文件是獨立模塊,通過module.exports暴露成員,require加載模塊。 - 特點
- 運行時加載:運行時動態加載,代碼執行到require語句時,才會讀取目標文件、執行模塊代碼、返回module.exports對象;
- 值傳遞:基礎類型是值的拷貝, 對象類型則是引用傳遞,require時會將module.exports的屬性值拷貝,后續導出模塊修改基礎類型值,導入模塊不會同步變化;
- 緩存模塊:模塊首次加載后,會緩存module.exports對象,后續require同一模塊時,直接返回緩存結果,避免重復執行模塊代碼;
- 運行環境:主要用于 Node.js,瀏覽器端需通過 Browserify、Webpack 等工具轉換為全局變量或 ESM。
AMD(Asynchronous Module Definition)
面(mian)向瀏覽器端的異(yi)步模(mo)塊(kuai)化方案,解決瀏覽器加載模(mo)塊(kuai)時 “阻塞頁面(mian)渲染” 問題,代表實現為 RequireJS;
- 設計目標
- 解決瀏覽器端同步加載模塊阻塞頁面渲染問題,通過異步方式加載模塊,加載完成后執行回調函數;
- 支持依賴前置(提前聲明所有依賴),確保模塊執行時依賴已加載完成。
- 特點
- 異步加載: 通過
<script>標簽動態創建請求加載模塊,加載完成后觸發回調函數,不阻塞 HTML 解析與頁面渲染; - 依賴前置:模塊定義時需提前聲明所有依賴,如define(['jquery'], ...),RequireJS 會先加載依賴模塊,所有依賴加載完成后,再執行當前模塊的工廠函數;
- 運行環境:僅支持瀏覽器端,Node 端需通過amd-loader等工具轉換;
- 支持多種路徑寫法:支持相對路徑(如./moduleA)、絕對路徑(如/js/moduleA)、模塊別名(如jquery)。
- 異步加載: 通過
UMD(Universal Module Definition)
通用(yong)(yong)模塊定義,兼容(rong) ESM、CJS、AMD 三種方案,可在瀏覽器與(yu) Node 端通用(yong)(yong),主要用(yong)(yong)于(yu)第(di)三方庫發布。
- 設計目標
? 解決第三方庫需適配多模塊方案的問題,使用一套代碼就能夠同時兼容 ESM、CJS、AMD 三種方案,可在瀏覽器與 Node 端通用,避免為不同模塊方案單獨發布代碼,降低維護成本。 - 特點
- 多環境適配:可在Node和瀏覽器端使用,檢測方式為
typeof define === 'function' && define.amd檢測 AMD 環境,typeof module === 'object' && module.exports檢測 CJS 環境,否則降級為全局變量;
- 多環境適配:可在Node和瀏覽器端使用,檢測方式為
ESM(ES6 Module)
ES6 官方標準化方案,兼顧瀏覽器與 Node 端,采用編譯時靜態加載,支持 Tree-Shaking,通過 import/export 語法實現。
- 設計目標
- ES6 官方統一瀏覽器與 Node 端的模塊化方案,解決傳統方案碎片化問題;
- 基于編譯時靜態加載設計,支持 Tree-Shaking。
- 特點
- 編譯時靜態加載:JavaScript 引擎在編譯階段分析模塊依賴,構建依賴樹,不執行模塊代碼,因為是靜態編譯的時候做了分析,所以很自然的支持 Tree Shaking。
- 值的引用傳遞:導入的成員是對導出模塊成員的引用,若導出模塊修改非const變量,導入模塊會同步變化。
- 默認啟用嚴格模式:禁止未聲明的變量使用。
本文(wen)首發于,公眾號訂閱請(qing)關注:

