Module Pattern

什麼是 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 來建構也是可以的,但會發生一些非預期的結果,並不建議使用。

備註

閉包(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

優缺點?

優點是清楚的物件導向、封裝概念。

缺點是

後記

自從離開上一份十分忙碌的工作後,終於有時間好好針對 JavaScript 這個部份充電,於是來看「JavaScript Patterns(中譯:JavaScript 設計模式)」這本書,也將自己的學習歷程記錄下來。歡迎大家討論,無論是有錯糾正或新知分享等。

推薦閱讀


這篇文章的原始位置在這裡-JavaScript - Module Pattern

由於部落格搬遷至此,因此在這裡放了一份,以便閱讀;部份文章片段也做了些許修改,以期提供更好的內容。

javascript closure 閉包 Design Pattern javascript ReferenceError 設計模式