Module Pattern
18 Jun 2015什麼是 Module Pattern?解決什麼問題?
Module Pattern 利用函數的「閉包(Closure)」特性來避免汙染全域的問題,意即使用閉包來提供封裝的功能,將方法和變數限制在一個範圍內存取與使用。這樣的好處除了避免汙染全域外,也將實作隱藏起來,只提供公開的介面(Public API)供其他地方使用。
物件實字(Object Literals)
第一個先來看物件實字的概念。
在 Object Literals Notation 中,物件中的屬性和方法用這樣的方式被描述 - Name / Value Pair,即 Key / Value 的方式,並用分號(:)區隔。每個 Name / Value Pair 用逗號(,)區隔,而最後一個 Name / Value Pair 後不需使用逗號結尾。最外層使用大括號({})包裝起來。
範例如下。
var myObjectLiteral = {
variableKey: variableValue,
functionKey: function() {
// ...
},
};
使用 Object Literal 的好處是將程式碼封裝和組織起來。如果用 new 來建構也是可以的,但會發生一些非預期的結果,並不建議使用。
備註
- JavaScript 並沒有如同我們熟知的其他語言(例如:Java、C# 等)有 private、protected 和 public 這些語法可用,而要靠函數的作用域,即閉包來實作。
閉包(Closure)
再來看閉包的觀念。
Closure 是指變數的生命週期只存在於該 function 中,一旦離開了 function,該變數就會被回收而不可再利用,且必須在 function 內事先宣告。
function closure() {
var a = 1;
console.log(a); // 1
}
closure();
console.log(a); // Uncaught ReferenceError: a is not defined
範例
來看一個 Module Pattern 的實際範例。
var testModule = (function() {
var counter = 0;
return {
incrementCounter: function() {
return counter++;
},
resetCounter: function() {
console.log('counter value prior to reset: ' + counter);
counter = 0;
},
};
})();
// test
testModule.incrementCounter();
testModule.resetCounter(); // counter value prior to reset: 1
在這裡,變數 counter 是個 private 變數,無法被 function 外的其他地方任意存取,只能經由公開方法 incrementCounter 和 resetCounter 取用。 function 最後會回傳一個物件,這個物件即是公開出去的 API,讓程式的其他區域可以與之互動。 而這就是利用函數的閉包特性來達成的。
再看一個更複雜的例子。
var basketModel = (function() {
// private
var basket = [];
function doSomethingPrivate() {
//...
}
function doSomethingElsePrivate() {
//...
}
// public
return {
addItem: function(value) {
basket.push(value);
},
getItemCount: function() {
return basket.length;
},
doSomething: doSomethingPrivate(),
getTotal: function() {
var q = this.getItemCount(),
p = 0;
while (q--) {
p = p + basket[q].price;
}
return p;
},
};
})();
basketModel.addItem({
item: 'bread',
price: 0.5,
});
basketModel.addItem({
item: 'butter',
price: 0.3,
});
console.log(basketModel.getItemCount()); // 2
console.log(basketModel.getTotal()); // 0.8
而這樣的存取是不行的…
console.log(basketModule.basket); // basket 是 private variable,不提供函數外部存取
console.log(basket); // basket 在函數內部的時候才可以這樣呼叫
總結,為 Module Pattern 做一個範本,如下。
var myNamespace = (function() {
// private members
var myPrivateVariable = 0;
var myPrivateMethod = function(someText) {
console.log(someText);
};
// public members
return {
myPublicVariable: 'foo',
myPublicFunction: function(bar) {
myPrivateVariable++;
myPrivateMethod(bar);
},
};
})();
console.log(myNamespace.myPublicVariable); // foo
myNamespace.myPublicFunction('hi'); // hi
優缺點?
優點是清楚的物件導向、封裝概念。
缺點是
- 如果要變數或方法的 public / private 狀態,我們必須要去用到的每一個地方手動修改使用方式。
- 在 debug 時,對於 private members 較難偵測。
- private members 難以擴充,彈性不高。
後記
自從離開上一份十分忙碌的工作後,終於有時間好好針對 JavaScript 這個部份充電,於是來看「JavaScript Patterns(中譯:JavaScript 設計模式)」這本書,也將自己的學習歷程記錄下來。歡迎大家討論,無論是有錯糾正或新知分享等。
推薦閱讀
- JavaScript Module Pattern: In-Depth:對於閉包進一步的探討可參考這篇文章。
- JavaScript Patterns 閱讀筆記 (5) Object Creation Patterns
這篇文章的原始位置在這裡-JavaScript - Module Pattern
由於部落格搬遷至此,因此在這裡放了一份,以便閱讀;部份文章片段也做了些許修改,以期提供更好的內容。