模組模式(Module Pattern):用閉包實現封裝
本篇延續前兩篇對閉包的討論。前面確立了閉包的定義(函式保有對原始範疇變數的活連結),以及閉包關閉的是變數而非值。模組模式是閉包最重要的應用之一,也是本篇的核心主題。
命名空間不是模組
在進入模組模式之前,Kyle Simpson 先釐清一個常見的混淆:把資料與函式收進一個物件,不是模組。
javascript
var workshop = {
teacher: "Kyle",
ask(question) {
console.log(this.teacher, question);
},
};
workshop.ask("Is this a module?");
// Kyle Is this a module?這個做法有個非正式的名字:命名空間模式(namespace pattern)。它確實把相關的資料和行為收集在一起,但所有屬性都是公開可存取的,workshop.teacher 可以從外部直接讀取和修改。
命名空間缺少的是封裝(encapsulation)。封裝的意思是:有些東西是公開的(public API),有些東西是私有的,外部無法存取。只要沒有這種可見性控制,就不算是模組。
模組需要封裝,封裝需要閉包
模組模式的核心要求:
- 封裝:區分公開介面與私有狀態
- 閉包:沒有閉包,就無法實現封裝
ES6 的原生模組語法在概念上也應視為閉包的運作。閉包是模組模式能存在的根本原因。
經典模組模式(Classic / Revealing Module Pattern)
這個模式由 Doug Crockford 在大約 2001 年前後整理並推廣,由兩個必要組成構成:
第一個組成:外層封閉函式(通常是 IIFE)
外層函式建立出一個獨立的範疇,讓私有狀態得以存在於其中。
第二個組成:內層函式對私有變數形成閉包
內層函式存取外層範疇的變數,使該範疇在函式執行完畢後不被垃圾回收。
javascript
var workshop = (function Module(teacher) {
var publicAPI = { ask };
return publicAPI;
// **********
function ask(question) {
console.log(teacher, question);
}
})("Kyle");
workshop.ask("It's a module, right?");
// Kyle It's a module, right?這裡的關鍵細節:
teacher是私有狀態,workshop.teacher從外部無法存取ask函式對teacher形成閉包,使外層範疇持續存活- 回傳的
publicAPI物件只暴露ask,這就是最小化公開介面
因為使用了 IIFE,這個模組只執行一次,是一種單例(singleton)。它執行後「結束了」,但由於閉包的存在,其範疇並未消失,私有狀態持續保留。
模組工廠函式(Module Factory Function)
IIFE 版本只能產生一個實例。如果需要多個獨立的模組實例,可以改用普通函式:
javascript
function WorkshopModule(teacher) {
var publicAPI = { ask };
return publicAPI;
// **********
function ask(question) {
console.log(teacher, question);
}
}
var workshop = WorkshopModule("Kyle");
workshop.ask("It's a module, right?");
// Kyle It's a module, right?每次呼叫 WorkshopModule 都會建立一個全新的範疇,產生一個擁有自己私有狀態的獨立模組實例,各實例之間互不干擾。這類函式稱為工廠函式(factory function)。
模組的本質:追蹤並控制隨時間變化的狀態
Kyle Simpson 提出一個判斷標準:
如果一個東西叫做模組,但它沒有任何會隨時間改變的狀態,那它不是模組,只是一個過度設計的命名空間。
模組模式的目的就是:封裝某些會隨時間變化的私有狀態,並透過最小化的公開 API 控制對這些狀態的存取。這正是前幾篇討論過的「最小暴露原則(Principle of Least Exposure)」的具體實踐。
複習
在 JavaScript 中建立模組的關鍵需求是什麼?
閉包是實作模組不可缺少的條件,它使封裝成為可能,讓資料與行為得以隱藏在內部。
經典模組模式的兩個核心組成是什麼?
第一是外層封閉函式(通常是 IIFE),第二是對外層變數形成閉包的內層函式,兩者共同建立出帶有私有狀態的範疇。
模組模式在 JavaScript 中的主要目的是什麼?
透過封裝資料與行為來追蹤並控制隨時間變化的狀態,只對外暴露最小必要的公開 API,內部細節保持私有。
模組與單純命名空間的根本差異是什麼?
模組透過資訊隱藏提供封裝,並且擁有可隨時間改變的狀態;命名空間只是把相關函式和資料組合在一起,並無隱藏機制。
在 JavaScript 中建立模組有哪兩種方式?
單例模組(使用只執行一次的 IIFE)以及模組工廠函式(可多次呼叫,每次產生獨立的模組實例)。
小測驗
模組模式與單純命名空間最根本的差異是什麼?
封裝與資訊隱藏實作模組模式不可或缺的語言機制是什麼?
閉包模組模式的主要目的是什麼?
追蹤並控制隨時間變化的狀態模組模式體現了哪個設計原則?
最小暴露(最小權限)原則哪種函式可以建立多個獨立的模組實例?
工廠函式(factory function)此文章是 FrontendMasters 上的 Deep JavaScript Foundations, v3 課程筆記
