深刻理解JavaScript 模塊模式

英文:http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth php

模塊模式是JavaScript一種經常使用的編碼模式。這是通常的理解,但也有一些高級應用沒有獲得不少關注。在本文中,我將回顧基礎知識,瀏覽一些不錯的高級技巧,甚至我認爲是原生基礎的。

基礎知識

首先咱們開始簡單概述模型模式。三年前Eric Miraglia(YUI)的博文使模型模式衆所周知。若是你已經很熟悉模型模式,能夠直接閱讀「高級模式」。

匿名閉包

這是一切成爲可能的基礎,也是JavaScript最好的特性。咱們將簡單的建立匿名函數,並當即執行。全部函數內部代碼都在閉包(closure)內。它提供了整個應用生命週期的私有和狀態。

html

  1. (function () {html5

  2.         // ... all vars and functions are in this scope onlyajax

  3.         // still maintains access to all globals設計模式

  4. }());安全

複製代碼閉包


注意匿名函數週圍的()。這是語言的要求。關鍵字function通常認爲是函數聲明,包括()就是函數表達式。

引入全局

JavaScript有個特性,稱爲隱性全局。使用變量名稱時,解釋器會從做用域向後尋找變量聲明。若是沒找到,變量會被假定入全局(之後能夠全局調用)。若是會被分配使用,在還不存在時全局建立它。這意味着在匿名函數裏使用全局變量很簡單。不幸的是,這會致使代碼難以管理,文件中不容易區分(對人而言)哪一個變量是全局的。

幸虧,匿名函數還有一個不錯的選擇。全局變量做爲參數傳遞給匿名函數。將它們引入咱們的代碼中,既更清晰,又比使用隱性全局更快。下面是一個例子:

異步

  1. (function ($, YAHOO) {ide

  2.         // 當前域有權限訪問全局jQuery($)和YAHOO模塊化

  3. }(jQuery, YAHOO));

複製代碼


模塊出口

有時你不僅想用全局變量,但你須要先聲明他們(模塊的全局調用)。咱們用匿名函數的返回值,很容易輸出他們。這樣作就完成了基本的模塊模式。如下是一個完整例子:

  1. var MODULE = (function () {

  2.         var my = {},

  3.                 privateVariable = 1;

  4.         

  5.         function privateMethod() {

  6.                 // ...

  7.         }

  8.         

  9.         my.moduleProperty = 1;

  10.         my.moduleMethod = function () {

  11.                 // ...

  12.         };

  13.         

  14.         return my;

  15. }());

複製代碼


注意,咱們聲明瞭一個全局模塊MODULE,有兩個公開屬性:方法MODULE.moduleMethod和屬性MODULE.moduleProperty。並且,匿名函數的閉包還維持了私有內部狀態。同時學會之上的內容,咱們就很容易引入須要的全局變量,和輸出到全局變量。

高級模式

對許多用戶而言以上的還不足,咱們能夠採用如下的模式創造強大的,可擴展的結構。讓咱們使用MODULE模塊,一個一個繼續。

擴充

模塊模式的一個限制是整個模塊必須在一個文件裏。任何人都瞭解長代碼分割到不一樣文件的必要。還好,咱們有很好的辦法擴充模塊。(在擴充文件)首先咱們引入模塊(從全局),給他添加屬性,再輸出他。下面是一個例子擴充模塊:

  1. var MODULE = (function (my) {

  2.         my.anotherMethod = function () {

  3.                 // 此前的MODULE返回my對象做爲全局輸出,所以這個匿名函數的參數MODULE就是上面MODULE匿名函數裏的my

  4.         };


  5.         return my;

  6. }(MODULE));

複製代碼


咱們再次使用var關鍵字以保持一致性,雖然其實不必。代碼執行後,模塊得到一個新公開方法MODULE.anotherMethod。擴充文件沒有影響模塊的私有內部狀態。

鬆耦合擴充

上面的例子須要咱們首先建立模塊,而後擴充它,這並不老是必要的。提高JavaScript應用性能最好的操做就是異步加載腳本。於是咱們能夠建立靈活多部分的模塊,能夠將他們無順序加載,以鬆耦合擴充。每一個文件應有以下的結構:

  1. var MODULE = (function (my) {

  2.         // add capabilities...

  3.         

  4.         return my;

  5. }(MODULE || {}));

複製代碼


這個模式裏,var語句是必須的,以標記引入時不存在會建立。這意味着你能夠像LABjs同樣同時加載全部模塊文件而不被阻塞。

緊耦合擴充

雖然鬆耦合很不錯,但模塊上也有些限制。最重要的,你不能安全的覆寫模塊屬性(由於沒有加載順序)。初始化時也沒法使用其餘文件定義的模塊屬性(但你能夠在初始化後運行)。緊耦合擴充意味着一組加載順序,可是容許覆寫。下面是一個例子(擴充最初定義的MODULE):

  1. var MODULE = (function (my) {

  2.         var old_moduleMethod = my.moduleMethod;

  3.         

  4.         my.moduleMethod = function () {

  5.                 // method override, has access to old through old_moduleMethod...

  6.         };

  7.         

  8.         return my;

  9. }(MODULE));

複製代碼


咱們覆寫的MODULE.moduleMethod,但依舊保持着私有內部狀態。

克隆和繼承

  1. var MODULE_TWO = (function (old) {

  2.         var my = {},

  3.                 key;

  4.         

  5.         for (key in old) {

  6.                 if (old.hasOwnProperty(key)) {

  7.                         my[key] = old[key];

  8.                 }

  9.         }

  10.         

  11.         var super_moduleMethod = old.moduleMethod;

  12.         my.moduleMethod = function () {

  13.                 // override method on the clone, access to super through super_moduleMethod

  14.         };

  15.         

  16.         return my;

  17. }(MODULE));

複製代碼


這種方式也許最不靈活。他能夠實現巧妙的組合,可是犧牲了靈活性。正如我寫的,對象的屬性或方法不是拷貝,而是一個對象的兩個引用。修改一個會影響其餘。這可能能夠保持遞歸克隆對象的屬性固定,但沒法固定方法,除了帶eval的方法。不過,我已經完整的包含了模塊。(其實就是作了一次淺拷貝)。

跨文件私有狀態

一個模塊分割成幾個文件有一個嚴重缺陷。每一個文件都有自身的私有狀態,且無權訪問別的文件的私有狀態。這能夠修復的。下面是一個鬆耦合擴充的例子,不一樣擴充文件之間保持了私有狀態:

  1. var MODULE = (function (my) {

  2.         var _private = my._private = my._private || {},

  3.                 _seal = my._seal = my._seal || function () {

  4.                         delete my._private;

  5.                         delete my._seal;

  6.                         delete my._unseal;

  7.                 },//模塊加載後,調用以移除對_private的訪問權限

  8.                 _unseal = my._unseal = my._unseal || function () {

  9.                         my._private = _private;

  10.                         my._seal = _seal;

  11.                         my._unseal = _unseal;

  12.                 };//模塊加載前,開啓對_private的訪問,以實現擴充部分對私有內容的操做

  13.         

  14.         // permanent access to _private, _seal, and _unseal

  15.         

  16.         return my;

  17. }(MODULE || {}));

複製代碼


何文件均可以在本地的變量_private中設置屬性,他會對別的擴充當即生效(即初始化時全部擴充的私有狀態都保存在_private變量,並被my._private輸出)。模塊徹底加載了,應用調用MODULE._seal()方法阻止對私有屬性的讀取(幹掉my._private輸出)。若是此後模塊又須要擴充,帶有一個私有方法。加載擴充文件前調用MODULE._unseal()方法(恢復my._private,外部恢復操做權限)。加載後調用再seal()。

這個模式一直隨我工做至今,我還沒看到別的地方這樣作的。我以爲這個模式頗有用,值得寫上。

子模塊

最後的高級模式實際上最簡單。有不少好方法建立子模塊。和建立父模塊是同樣的:

  1. MODULE.sub = (function () {

  2.         var my = {};

  3.         // 就是多一級命名空間

  4.         

  5.         return my;

  6. }());

複製代碼


雖然很簡單,但我仍是提一下。子模塊有全部正常模塊的功能,包括擴充和私有狀態。

總結

大多數高級模式能夠互相組合成更多有用的模式。若是要我提出一個複雜應用的設計模式,我會組合鬆耦合、私有狀態和子模塊。

這裏我尚未涉及性能,不過我有個小建議:模塊模式是性能增益的。他簡化了許多,加快代碼下載。鬆耦合能夠無阻塞並行下載,等價於提升下載速度。可能初始化比別的方法慢一點,但值得權衡。只要全局正確的引入,運行性能不會有任何損失,可能還由於局部變量和更少的引用,加快子模塊的加載。

最後,一個例子動態加載子模塊到父模塊(動態建立)中。這裏就不用私有狀態了,其實加上也很簡單。這段代碼容許整個複雜分紅的代碼核心及其子模塊等平行加載徹底。

  1. var UTIL = (function (parent, $) {

  2.         var my = parent.ajax = parent.ajax || {};

  3.         

  4.         my.get = function (url, params, callback) {

  5.                 // ok, so I'm cheating a bit :)

  6.                 return $.getJSON(url, params, callback);

  7.         };

  8.         

  9.         // etc...

  10.         

  11.         return parent;

  12. }(UTIL || {}, jQuery));

複製代碼


我但願你能受益的,並請發表評論,分享您的想法。 如今,繼續前進,並寫出更好的,更模塊化的JavaScript!

相關文章
相關標籤/搜索