JavaScript - Module Pattern

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外的其他地方任意存取,只能經由公開方法incrementCounterresetCounter取用。 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

留言