Revealing Module (闡明模組)模式其實可算是 Module 模式的進階版本,Revealing Module 是由 Christian Heilmann 所提出的,接下來就來看看此模式的問題與解決方案。
在複雜應用程式中,我們需要同時管理私有狀態和函式,並揭露外部可使用、存取的方法,以避免全域污染,又同時提高程式碼的可重用性。
如何在不暴露模組內部所有細節的情況下,允許外部訪問模組的特定功能或變數?在沒有封裝機制的情況下,模組內的變數和函式都是公開的,這可能導致不安全或不可預測的程式碼行為。
此問題解法是 Revealing Module,在介紹 Revealing Module 模式之前,不知道大家有沒有跟我一樣的困惑,覺得上述的問題用我們昨天提過的 Module 模式就可以解決了啊? 在此之前,想先稍微介紹一下,在 ES6 模組功能出現以前,JavaScript 是如何管理私有和公共變數與方法的,因 JavaScript 沒有 private
和 public
語法,因此是透過 closure 的方式將函式與變數限制在特定範圍內,將私有變數限制在函式作用域內,將外部可存取的 return 出去,一般 Module 用 IIFE 的範例如下:
const myGeneralModule = (function () {
// 私有變數
const privateVariable = 'I am private';
return {
// 公共方法
publicMethod: function () {
console.log(privateVariable);
},
publicHello: function () {
console.log('hello')
}
};
})();
myGeneralModule.publicMethod(); // I am private
myGeneralModule.publicHello(); // hello
myGeneralModule.privateVariable; // undefined,無法存取私有變數
可以看到我們透過立即執行函式來建立函式作用域,將私有變數包在函式內,只拿到 return 的值並依此執行公有方法,且無法存取到私有變數,但一般 Module 有個小問題,就是我們需要在 return 的時候才會宣告要揭露的公有函式內容,會讓整個結構稍微凌亂些。
而 Revealing Module 對應上述 Module 範例,會將所有變數和函式先定義好,在最後 return 時才會指定要揭露哪些變數和函式,我們可以依照以下步驟來實現 Revealing Module:
Revealing Module 程式碼範例如下:
const myRevealingModule = (function () {
// 私有變數
const privateVariable = 'I am private';
const publicMethod = function () {
console.log(privateVariable);
};
const publicHello = function () {
console.log('hello')
}
// 在最後回傳時決定要揭露的方法,也可另外再取其他名稱
return {
printVariable: publicMethod,
printHello: publicHello
};
})();
myRevealingModule.printVariable(); // I am private
myRevealingModule.printHello(); // hello
簡單來說,Revealing Module 會在程式碼最後再決定要揭露的變數和函式,如果要看模組的公開接口有哪些,直接看程式碼尾端即可,讓整體程式碼更一致、好維護也好理解,不過上述都還在 IIFE 階段,但我們現在有了 ES6 的 import
和 export
語法啦! 在 ES6 模組中,我們不需要再用 return 什麼來區分公有和私有變數,而是只要有 export
才算公有、沒有 export
一律視為私有,因此 Revealing Module 應用在 ES6 模組寫法,其實就是把要匯出的東西統一在程式碼最下面一起export
:
let counter = 0;
let userName = 'Foo';
const publicAddCount = () => {
counter++
};
const publicGetUserName = () => {
console.log(`user name is ${userName}`);
};
const publicSetUserName = (newUserName) => {
userName = newUserName;
};
// 最後統一匯出外部可存取的方法,也可更改名稱
const myRevealingModule = {
addCount: publicAddCount,
getUserName: publicGetUserName,
setUserName: publicSetUserName
}
export default myRevealingModule
在其他檔案就可以這樣使用:
import myRevealingModule from './myRevealingModule';
myRevealingModule.getUserName();
以 Revealing Module 作為解決方案的優點如下:
以 Revealing Module 作為解決方案的缺點如下:
export
就可匯出了其他缺點我覺得昨天提到的,一般的 Module 模式也有類似問題,但也列上來~
以我自己目前撰寫模組的習慣,比較少聚集在最後一起 export
,通常都是定義變數和函式時,直接在需要的地方前面加個 export
語法就匯出了,不過這樣的確也會讓程式碼比較零散,有的有 export
有的沒有,也會比較難看出這到底有沒有 export
讓外部使用,因此若對程式碼結構一致性或可讀性有要求,或許可考慮 Revealing Module 的方式。