模組模式是JavaScript一種常用的編碼模式。這是一般的理解,但也有一些進階應用程式沒有得到很多關注。在本文中,我將回顧基礎知識,瀏覽一些不錯的進階技巧,甚至我認為是原生基礎的。 基礎知識 首先我們開始簡單概述模型模式。三年前Eric Miraglia(YUI)的博文使模型模式眾所周知。如果你已經很熟悉模型模式,可以直接閱讀“進階模式”。 匿名閉包 這是一切成為可能的基礎,也是JavaScript最好的特性。我們將簡單的建立匿名函數,並立即執行。所有函數內部代碼都在閉包(closure)內。它提供了整個應用生命週期的私人和狀態。
- (function () {
- // ... all vars and functions are in this scope only
- // still maintains access to all globals
- }());
複製代碼 注意匿名函數周圍的()。這是語言的要求。關鍵字function一般認為是函式宣告,包括()就是函數運算式。 引入全域 JavaScript有個特性,稱為隱性全域。使用變數名稱時,解譯器會從範圍向後尋找變數聲明。如果沒找到,變數會被假定入全域(以後可以全域調用)。如果會被分配使用,在還不存在時全域建立它。這意味著在匿名函數裡使用全域變數很簡單。不幸的是,這會導致代碼難以管理,檔案中不容易區分(對人而言)哪個變數是全域的。 幸好,匿名函數還有一個不錯的選擇。全域變數作為參數傳遞給匿名函數。將它們引入我們的代碼中,既更清晰,又比使用隱性全域更快。下面是一個例子:
- (function ($, YAHOO) {
- // 當前域有許可權訪問全域jQuery($)和YAHOO
- }(jQuery, YAHOO));
複製代碼 模組出口 有時你不只想用全域變數,但你需要先聲明他們(模組的全域調用)。我們用匿名函數的傳回值,很容易輸出他們。這樣做就完成了基本的模組模式。以下是一個完整例子:
- var MODULE = (function () {
- var my = {},
- privateVariable = 1;
-
- function privateMethod() {
- // ...
- }
-
- my.moduleProperty = 1;
- my.moduleMethod = function () {
- // ...
- };
-
- return my;
- }());
複製代碼 注意,我們聲明了一個全域模組MODULE,有兩個公開屬性:方法MODULE.moduleMethod和屬性MODULE.moduleProperty。而且,匿名函數的閉包還維持了私人內部狀態。同時學會之上的內容,我們就很容易引入需要的全域變數,和輸出到全域變數。 進階模式 對許多使用者而言以上的還不足,我們可以採用以下的模式創造強大的,可擴充的結構。讓我們使用MODULE模組,一個一個繼續。 擴充 模組模式的一個限制是整個模組必須在一個檔案裡。任何人都瞭解長代碼分割到不同檔案的必要。還好,我們有很好的辦法擴充模組。(在擴充檔案)首先我們引入模組(從全域),給他添加屬性,再輸出他。下面是一個例子擴充模組:
- var MODULE = (function (my) {
- my.anotherMethod = function () {
- // 此前的MODULE返回my對象作為全域輸出,因此這個匿名函數的參數MODULE就是上面MODULE匿名函數裡的my
- };
- return my;
- }(MODULE));
複製代碼 我們再次使用var關鍵字以保持一致性,雖然其實沒必要。代碼執行後,模組獲得一個新公開方法MODULE.anotherMethod。擴充檔案沒有影響模組的私人內部狀態。 松耦合擴充
上面的例子需要我們首先建立模組,然後擴充它,這並不總是必要的。提升JavaScript應用效能最好的操作就是非同步載入指令碼。因而我們可以建立靈活多部分的模組,可以將他們無順序載入,以松耦合擴充。每個檔案應有如下的結構:
- var MODULE = (function (my) {
- // add capabilities...
-
- return my;
- }(MODULE || {}));
複製代碼 這個模式裡,var語句是必須的,以標記引入時不存在會建立。這意味著你可以像LABjs一樣同時載入所有模組檔案而不被阻塞。 緊耦合擴充 雖然松耦合很不錯,但模組上也有些限制。最重要的,你不能安全的覆寫模組屬性(因為沒有載入順序)。初始化時也無法使用其他檔案定義的模組屬性(但你可以在初始化後運行)。緊耦合擴充意味著一組載入順序,但是允許覆寫。下面是一個例子(擴充最初定義的MODULE):
- var MODULE = (function (my) {
- var old_moduleMethod = my.moduleMethod;
-
- my.moduleMethod = function () {
- // method override, has access to old through old_moduleMethod...
- };
-
- return my;
- }(MODULE));
複製代碼 我們覆寫的MODULE.moduleMethod,但依舊保持著私人內部狀態。 複製和繼承
- var MODULE_TWO = (function (old) {
- var my = {},
- key;
-
- for (key in old) {
- if (old.hasOwnProperty(key)) {
- my[key] = old[key];
- }
- }
-
- var super_moduleMethod = old.moduleMethod;
- my.moduleMethod = function () {
- // override method on the clone, access to super through super_moduleMethod
- };
-
- return my;
- }(MODULE));
複製代碼 這種方式也許最不靈活。他可以實現巧妙的組合,但是犧牲了靈活性。正如我寫的,對象的屬性或方法不是拷貝,而是一個對象的兩個引用。修改一個會影響其他。這可能可以保持遞迴複製對象的屬性固定,但無法固定方法,除了帶eval的方法。不過,我已經完整的包含了模組。(其實就是做了一次淺拷貝)。 跨檔案私人狀態 一個模組分割成幾個檔案有一個嚴重缺陷。每個檔案都有自身的私人狀態,且無權訪問別的檔案的私人狀態。這可以修複的。下面是一個松耦合擴充的例子,不同擴充檔案之間保持了私人狀態:
- var MODULE = (function (my) {
- var _private = my._private = my._private || {},
- _seal = my._seal = my._seal || function () {
- delete my._private;
- delete my._seal;
- delete my._unseal;
- },//模組載入後,調用以移除對_private的存取權限
- _unseal = my._unseal = my._unseal || function () {
- my._private = _private;
- my._seal = _seal;
- my._unseal = _unseal;
- };//模組載入前,開啟對_private的訪問,以實現擴充部分對私人內容的操作
-
- // permanent access to _private, _seal, and _unseal
-
- return my;
- }(MODULE || {}));
複製代碼 何檔案都可以在本地的變數_private中設定屬性,他會對別的擴充立即生效(即初始化時所有擴充的私人狀態都儲存在_private變數,並被my._private輸出)。模組完全載入了,應用調用MODULE._seal()方法阻止對私人屬性的讀取(幹掉my._private輸出)。如果此後模組又需要擴充,帶有一個私人方法。載入擴充檔案前調用MODULE._unseal()方法(恢複my._private,外部恢複操作許可權)。載入後調用再seal()。 這個模式一直隨我工作至今,我還沒看到別的地方這樣做的。我覺得這個模式很有用,值得寫上。 子模組 最後的進階模式實際上最簡單。有很多好方法建立子模組。和建立父模組是一樣的:
- MODULE.sub = (function () {
- var my = {};
- // 就是多一級命名空間
-
- return my;
- }());
複製代碼 雖然很簡單,但我還是提一下。子模組有所有正常模組的功能,包括擴充和私人狀態。 總結 大多數進階模式可以互相組合成更多有用的模式。如果要我提出一個複雜應用的設計模式,我會組合松耦合、私人狀態和子模組。 這裡我還沒有涉及效能,不過我有個小建議:模組模式是效能增益的。他簡化了許多,加快代碼下載。松耦合可以無阻塞並行下載,等價於提高下載速度。可能初始化比別的方法慢一點,但值得權衡。只要全域正確的引入,運行效能不會有任何損失,可能還因為局部變數和更少的引用,加快子模組的載入。 最後,一個例子動態載入子模組到父模組(動態建立)中。這裡就不用私人狀態了,其實加上也很簡單。這段代碼允許整個複雜分成的代碼核心及其子模組等平行載入完全。
- var UTIL = (function (parent, $) {
- var my = parent.ajax = parent.ajax || {};
-
- my.get = function (url, params, callback) {
- // ok, so I'm cheating a bit
- return $.getJSON(url, params, callback);
- };
-
- // etc...
-
- return parent;
- }(UTIL || {}, jQuery));
複製代碼 我希望你能受益的,並請發表評論,分享您的想法。 現在,繼續前進,並寫出更好的,更模組化的JavaScript! |