JavaScript - Module Pattern
自從離開上一份十分忙碌的工作後,終於有時間好好針對JavaScript這個部份充電,於是來看「JavaScript Patterns (中譯:JavaScript 設計模式)」這本書,也將自己的學習歷程記錄下來。歡迎大家討論,無論是有錯糾正或新知分享等。
什麼是Module Pattern?解決什麼問題?
Module Pattern 利用函數的「閉包(closure)」特性來避免汙染全域的問題 - 使用閉包(closure)來提供封裝的功能,將方法和變數限制在一個範圍內存取與使用。這樣的好處除了避免汙染全域外,也將實作隱藏起來,只提供公開的介面(public API)供其他地方使用,簡單易懂。
物件實字(object literals)
第一個先來看物件實字(object literals)的觀念。
在object literal notation中,物件中的屬性和方法用這樣的方式被描述 - name/value pairs,即key/value的方式,並用分號(:)區隔 - 每個name/value pairs用逗號(,)區隔,而最後一個name/value pair後不需使用逗號結尾 - 使用大括號({})包裝起來
範例如下。
var myObjectLiteral = {
variableKey: variableValue,
functionKey: function(){
// ...
}
};
使用object literal的好處是將程式碼封裝和組織起來。
如果用new來建構也是可以的,但會發生一些非預期的結果,並不建議使用。
[Note] JavaScript並沒有如同我們熟知的其他語言(例如:Java、C#等)有private、protected和public這些語法可用,而要靠函數的作用域,即「閉包(closure)」來實作。
閉包(closure)
再來看閉包(closure)的觀念。
Closure是指變數的生命週期只存在於該function中,一旦離開了function,該變數就會被回收而不可再利用,且必須在function內事先宣告。
function closure() {
var a = 1;
console.log(a); //1
}
closure();
console.log(a); //Uncaught ReferenceError: a is not defined
對於closure進一步的探討可參考這篇文章 Closures。
Module Pattern的範例
來看一個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最後會return一個物件,這個物件即是公開出去的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 Module Pattern: In-Depth。
推薦閱讀
由於部落格搬家了,因此在新落格也放了一份,未來若有增刪會在這裡更新-JavaScript: Module Pattern。
留言