JavaScript Modules (模組)
ES6 模組 (ES Modules, ESM) 是 JavaScript 官方的模組系統,讓你可以將程式碼分割成多個檔案,並在檔案之間 import(匯入)和 export(匯出)功能。
為什麼需要模組?
在沒有模組系統之前,所有 JavaScript 程式碼都在全域作用域 (global scope) 中執行,容易造成命名衝突和維護困難。模組系統解決了這些問題:
- 避免命名衝突:每個模組有自己的作用域
- 程式碼重用:可以輕鬆在不同專案間共享程式碼
- 依賴管理:明確知道程式碼依賴哪些其他模組
- 更好的組織:將程式碼按功能分割成小檔案
基本語法
export(匯出)
使用 export 關鍵字將變數、函式或類別匯出,讓其他模組可以使用。
Named Export(具名匯出)
// math.js
export var PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
也可以在檔案底部統一匯出:
// math.js
var PI = 3.14159;
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
export { PI, add, multiply };
Default Export(預設匯出)
每個模組只能有一個 default export:
// User.js
export default class User {
constructor(name) {
this.name = name;
}
greet() {
return 'Hello, ' + this.name;
}
}
// greeting.js
export default function greet(name) {
return 'Hello, ' + name;
}
import(匯入)
使用 import 關鍵字從其他模組匯入功能。
匯入 Named Export
// 匯入特定項目
import { add, multiply } from './math.js';
console.log(add(2, 3)); // 5
console.log(multiply(2, 3)); // 6
// 匯入並重新命名
import { add as sum, multiply } from './math.js';
console.log(sum(2, 3)); // 5
// 匯入全部並使用命名空間
import * as math from './math.js';
console.log(math.PI); // 3.14159
console.log(math.add(2, 3)); // 5
匯入 Default Export
// 匯入 default export,名稱可以自己取
import User from './User.js';
import greet from './greeting.js';
var user = new User('Mike');
console.log(greet('World'));
混合匯入
// 同時匯入 default 和 named exports
import User, { validateEmail, formatName } from './User.js';
在 HTML 中使用模組
在瀏覽器中使用 ES Modules,需要在 <script> 標籤加上 type="module":
<!DOCTYPE html>
<html>
<head>
<title>ES Modules Demo</title>
</head>
<body>
<script type="module">
import { add } from './math.js';
console.log(add(2, 3));
</script>
<!-- 或引入外部模組檔案 -->
<script type="module" src="./main.js"></script>
</body>
</html>
使用
type="module" 的 script 預設是 defer 的,會在 HTML 解析完成後才執行。模組的特性
模組作用域
模組內的變數預設是私有的,不會污染全域:
// counter.js
var count = 0; // 私有變數
export function increment() {
count++;
return count;
}
export function getCount() {
return count;
}
// main.js
import { increment, getCount } from './counter.js';
console.log(increment()); // 1
console.log(increment()); // 2
console.log(getCount()); // 2
console.log(count); // ReferenceError: count is not defined
嚴格模式
模組自動運行在嚴格模式 (strict mode) 下,不需要手動加上 'use strict'。
單例模式
模組只會被執行一次,即使被多次 import。這表示模組中的程式碼只會執行一次,狀態會被保留:
// config.js
console.log('config 模組被載入');
export var settings = { theme: 'dark' };
// a.js
import { settings } from './config.js';
settings.theme = 'light';
// b.js
import { settings } from './config.js';
console.log(settings.theme); // 'light'(被 a.js 修改過了)
config 模組被載入 只會輸出一次。
動態匯入
使用 import() 函式可以動態載入模組,它回傳一個 Promise:
// 條件載入
if (needFeature) {
import('./feature.js').then(function(module) {
module.doSomething();
});
}
// 使用 async/await
async function loadModule() {
var module = await import('./feature.js');
module.doSomething();
}
動態匯入適合用於:
- 按需載入(lazy loading)
- 根據條件載入不同模組
- 載入路徑需要動態決定時
重新匯出(Re-export)
可以從一個模組重新匯出另一個模組的內容,常用於建立統一的入口點:
// utils/index.js
export { add, multiply } from './math.js';
export { formatDate } from './date.js';
export { default as User } from './User.js';
// main.js
import { add, formatDate, User } from './utils/index.js';
實際專案結構範例
project/
├── index.html
├── main.js
├── utils/
│ ├── index.js
│ ├── math.js
│ └── string.js
├── components/
│ ├── Button.js
│ └── Modal.js
└── services/
└── api.js
// utils/math.js
export function add(a, b) {
return a + b;
}
// utils/string.js
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
// utils/index.js - 統一匯出
export * from './math.js';
export * from './string.js';
// main.js
import { add, capitalize } from './utils/index.js';
CommonJS vs ES Modules
在 Node.js 環境中,你可能會看到另一種模組語法 CommonJS:
// CommonJS (Node.js 傳統語法)
var math = require('./math');
module.exports = { add: add };
// ES Modules (現代標準)
import { add } from './math.js';
export { add };
主要差異:
| 特性 | CommonJS | ES Modules |
|---|---|---|
| 載入時機 | 執行時 (runtime) | 解析時 (parse time) |
| 匯出 | module.exports | export |
| 匯入 | require() | import |
| 動態匯入 | 支援 | 需用 import() |
| 瀏覽器支援 | 需打包工具 | 原生支援 |
現代開發建議使用 ES Modules,它是 JavaScript 的官方標準,且瀏覽器原生支援。
注意事項
- 檔案路徑必須完整:在瀏覽器中使用時,import 路徑必須包含副檔名
.js
// 正確
import { add } from './math.js';
// 錯誤(在瀏覽器中)
import { add } from './math';
CORS 限制:使用
file://協定開啟 HTML 時,模組會因 CORS 政策而無法載入,需要使用本地伺服器循環依賴:模組可以有循環依賴,但要小心處理,避免取得 undefined 值