JavaScript 一開始是為了在網頁中加入互動效果而設計的腳本語言,隨著功能需求越來越複雜,程式碼的結構與可維護性也變得越來越重要。
這就是「模組化(Modularization)」誕生的背景。
🐻為什麼需要模組化?
早期的 JavaScript 開發常把所有程式碼寫在同一個 .js
檔案中,變數與函式都是全域的(global),這樣做會導致:
- 命名衝突:不同功能可能使用相同變數名稱而互相干擾。
- 可讀性差:所有邏輯糾結在一起,不容易閱讀與維護。
- 測試困難:函式間高度耦合,無法單獨測試。
模組化的核心目標是 將程式碼依照功能切分為獨立單元,彼此之間低耦合高內聚,方便重複使用、測試與維護。
🐻常見的模組化方式
IIFE(Immediately Invoked Function Expression)
- 最早期的模組化技巧,用來隔離變數作用域。
- 使用函式包住程式碼,並立即執行。
模組定義:
// greet-iife.js
(function () {
const name = "IT熊";
function sayHello() {
console.log(`Hello, ${name}`);
}
// 模擬暴露給外部
window.greet = {
sayHello
};
})();
呼叫方式:
<script src="greet-iife.js"></script>
<script>
greet.sayHello(); // 輸出: Hello, IT熊
</script>
優點:
- 解決全域命名污染問題。
- 實現「私有變數」的效果。
缺點:
- 沒有真正的模組系統,模組間的依賴要手動管理。
- 結構不夠清晰,容易產生技術債。
適用情境:
- 老舊專案、沒有模組工具的純前端網頁。
CommonJS(Node.js 預設模組系統)
- 每個
.js
文件就是一個模組。 - 使用
require()
載入模組,使用module.exports
輸出。
模組定義:
// greet-commonjs.js
const name = "IT熊";
function sayHello() {
console.log(`Hello, ${name}`);
}
module.exports = {
sayHello
};
呼叫方式:
// main.js (Node.js 執行環境)
const greet = require('./greet-commonjs.js');
greet.sayHello(); // 輸出: Hello, IT熊
優點:
- 模組語法簡單直觀。
- Node.js 生態系完整,NPM 模組幾乎都是用 CommonJS 撰寫。
缺點:
- 同步載入:在瀏覽器中不適用,會阻塞執行。
- 無法靜態分析(例如 Tree Shaking)。
適用情境:
- Node.js 應用程式,如 CLI 工具、伺服器端程式。
AMD(Asynchronous Module Definition)
- 為瀏覽器設計的模組規範,支援非同步載入。
- 使用
define()
與require()
,代表工具為 RequireJS。
模組定義:
// greet-amd.js
define([], function () {
const name = "IT熊";
function sayHello() {
console.log(`Hello, ${name}`);
}
return {
sayHello
};
});
呼叫方式:
<!-- 引入 RequireJS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script>
<script>
require(['greet-amd'], function (greet) {
greet.sayHello(); // 輸出: Hello, IT熊
});
</script>
優點:
- 支援非同步載入模組,適合瀏覽器端資源管理。
缺點:
- 語法繁瑣、學習曲線高。
- 現代開發工具(如 Webpack)幾乎取代其功能。
適用情境:
- 舊專案、RequireJS 建立的大型單頁應用(SPA)。
UMD(Universal Module Definition)
- 結合 CommonJS、AMD、與全域變數的模組定義方式。
- 可同時在 Node.js、RequireJS、瀏覽器環境運作。
模組定義:
// greet-umd.js
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory); // AMD
} else if (typeof module === 'object' && module.exports) {
module.exports = factory(); // CommonJS
} else {
root.greet = factory(); // Global
}
}(this, function () {
const name = "IT熊";
function sayHello() {
console.log(`Hello, ${name}`);
}
return {
sayHello
};
}));
呼叫方式:
(取決於環境)
在 HTML 中:
<script src="greet-umd.js"></script>
<script>
greet.sayHello(); // 輸出: Hello, IT熊
</script>
在 Node.js 中:
const greet = require('./greet-umd.js');
greet.sayHello(); // 輸出: Hello, IT熊
優點:
- 極高的相容性,適合開源函式庫發佈。
缺點:
- 編寫複雜,一般專案不需自行撰寫,通常透過工具產出。
適用情境:
- 跨平台函式庫 或 SDK(如 jQuery、Lodash 發佈格式)。
ES Modules(ESM,現代標準模組)
- 自 ES2015(ES6)起加入的原生模組語法。
- 使用
import
和export
,支援靜態分析與 Tree Shaking。
模組定義:
// greet-esm.js
const name = "IT熊";
export function sayHello() {
console.log(`Hello, ${name}`);
}
呼叫方式:
(取決於環境)
在 HTML 中:
<!-- 注意 type="module" -->
<script type="module">
import { sayHello } from './greet-esm.js';
sayHello(); // 輸出: Hello, IT熊
</script>
在 Node.js 中(package.json
中加 "type": "module"
[註1]):
// main.mjs
import { sayHello } from './greet-esm.js';
sayHello(); // 輸出: Hello, IT熊
優點:
- 標準語法,瀏覽器與 Node.js 都支援。
- 最佳效能,支援靜態分析與懶加載。
- 與打包工具(Webpack、Vite、Rollup)高度整合。
缺點:
- 較新的 Node.js 版本(>= 12)才全面支援。
- 若用在瀏覽器,需透過
type="module"
並處理 CORS 限制。
適用情境:
- 現代前端專案、React/Vue/Angular 應用程式、Node.js + ES 模組開發。
🐻模組化帶來什麼好處?
- 提升可維護性:功能單一的模組易於管理與除錯。
- 加速開發:多人協作時,各自負責不同模組。
- 提升效能:搭配打包工具(如 Webpack, Vite),能進行模組載入優化。
- 利於測試:模組化讓單元測試變得簡單。
🐻結語
模組化讓 JavaScript 從混亂走向秩序,是現代開發中不可或缺的觀念。無論你是前端工程師、Node.js 開發者,還是剛入門的學習者,了解模組化都能讓你的程式碼更具結構、更易維護。
🐻備註
[1] Node.js 一開始是用 CommonJS 作為模組系統(使用 require
/module.exports
),直到後來才開始支援 ES Modules(使用 import
/export
),但預設情況下仍是用 CommonJS。所以要在 Node.js 中使用 ES Modules 語法,就必須告訴 Node.js:「我要用 ESM!」
這就是為什麼你要在 package.json
裡加:
{
"name": "my-esm-project",
"type": "module"
}