Javascript “組件模式” 深入研究

一、前言

這段時間學習js,又看到一篇講javascript設計模式的好文章,嘗試翻譯出來,如果有什麼疏漏和錯誤,煩請各位不吝指出,謝謝~

原文地址:http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth

 

二、譯文

“組件模式”是一種很常用的Javascript編碼模式。雖然已經被廣泛的應用,但是還有一些高級的用途沒有被關注。在這邊文章裏,我將針對幾個比較特別的話題進行簡單的闡述,其中的一些我認爲應該是第一次被提及(作者原創)。

基礎

我們從一個簡單的組件模式開始說起,這個模式現在已經非常流行,最初是3年前由雅虎YUI的Eric Miraglia在其博客中提出的。如果你已經對這個模式很熟悉了,那麼可以直接跳到“高級模式”一節。

匿名閉包(Anonymous Closures)

這是使得模式可以正常工作的語言基礎,也實在是javascript最爲實用的特性之一。我們可以簡單的創建一個匿名函數,並且立刻執行它。所有運行在該函數裏的代碼叫做一個閉包,它提供了在整個應用程序生命週期中都有效的數據隱私控制以及狀態保存功能。

?
1
2
3
4
(function () {     
// ... all vars and functions are in this scope only       
// still maintains access to all globals
}());

 

請注意()包圍了匿名函數,這是語法的需要,因爲以function開頭的語句會被編譯器認爲的函數的定義,而加上()就變爲了創建了一個函數表達式。

全局導入(Global Import)

JavaScript 有一個大家熟知的特性叫做implied globals,就是當使用一個變量的時候,解釋器會沿着作用域鏈一直向上查找這個變量的定義,如果沒找到,這個變量就會自動成爲一個全局(global)變量。這意味着在一個匿名閉包裏去創建一個全局變量變得非常簡單。

但是,這產生了很難管理的代碼問題,因爲我們無法明顯的知道一個給定的文件裏究竟哪些變量是全局的

譯者注:如果允許這麼隱晦的寫法來創建全局變量,則代碼中使用的變量,很難知道是全局的還是局部的。)

幸運的是,我們的匿名函數提供了一個很簡單的修改來解決這個問題。我們傳入一個全局對象作爲匿名函數的參數,我們利用這個參數導入我們的變量到全局對象中,這樣做比隱式的聲明全局變量要乾淨和快速的多。下面是一個例子

 

?
1
2
3
(function ($, YAHOO) {       
// now have access to globals jQuery (as $) and YAHOO in this code
}(jQuery, YAHOO));
 

模塊導出(Module Export)

有時候我們不只是想定義一個模塊,還想直接使用它,我們可以使用匿名函數的返回值來實現。如果這麼做,就是實現了一個基本的“組件模式”,看下面的例子:

?
1
2
3
4
5
6
7
8
9
10
11
var MODULE = (function () {       
var my = {},privateVariable = 1;              
 function privateMethod() {               
// ...       
}
my.moduleProperty = 1;      
 my.moduleMethod = function () {               
// ...       
};               
return my;
}());

 

要注意的是我們定義了一個全局組件名字叫“MODULE”,他有兩個屬性:一個方法MODULE.moduleMethod以及一個屬性MODULE.moduleProperty。並且,它通過匿名函數所在的閉包維護了一組私有的內部狀態

譯者注:privateVariable變量和privateMethod方法都是私有的,外部無法訪問)。

這樣的話,我們就可以用上面的模式簡單的導入需要的全局變量。



高級模式(Advanced Patterns)

上面的模式已經可以解決應用中的許多問題,但是我們還可以在這個基礎上擴展出很多強大的,易擴展的結構。讓我們一個個看一下,繼續用我們的組件“MODULE”做例子

增益模式(Augmentation)

使用上面的模式的缺點和限制之一,就是整個模塊必須在一個文件裏。任何在大型項目中工作的人都會意識到能將代碼拆分成多個文件帶來的價值。幸運的是,我們有一個不錯的解決方案叫做“增益組件”。首先,我們導入組件,然後添加屬性,然後再將其導出。下面是一個例子,爲我們的MODULE組件添加增益特性:

?
1
2
3
4
5
6
var MODULE = (function (my) {       
my.anotherMethod = function () {               
// added method...       
};       
return my;
}(MODULE));

 

我們使用var關鍵字來保持一致性(儘管這不是必須的)。這段代碼執行過後,我們的組件就增加了一個公開的方法叫做MODULE.anotherMethod。這個增加的文件也會同時保持它自己的私有狀態。

譯者注:通過增益模式,一個雷被切分成獨立的文件,每個文件關注它內部需要的方法和屬性,並且當所有文件合併執行後,可以成爲一個完整的組件)

鬆耦合增益(Loose Augmentation)

我們上面的例子有一個缺點,就是需要MODULE模塊先被創建,然後再執行子文件中的增益。這是一種緊耦合的做法。javascript性能優化中通常的做法是腳本的異步加載,這樣我們就可以創建由多個文件組成的易擴展的組件,並且他們可以以任何順序來加載,這叫做鬆耦合增益模式:

?
1
2
3
4
var MODULE = (function (my) {       
// add capabilities...              
 return my;
}(MODULE || {}));

 

在這個模式中,var關鍵字是必須的。注意我們的import點(參數)會自動判斷組件是否存在,否則就創建一個空組件。這意味着你可以使用一些異步加載工具比如LABjs來並行加載你所有的js組件,而不需要block頁面。

緊耦合增益(Tight Augmentation)

“鬆耦合增益”模式非常棒,不過它也有一些限制,最重要的一個就是無法重寫屬性。你也無法在初始化組件的時候使用其他文件的內容(因爲加載順序無法保證),“緊耦合增益模式”需要一個有序的加載順序,但是允許你重寫其他文件的內容,這是一個簡單的例子:

?
1
2
3
4
5
6
7
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,同時也保存了舊的 MODULE.moduleMethod方法的引用(如果需要的話)

克隆和繼承(Cloning and Inheritance)

?
1
2
3
4
5
6
7
8
9
10
11
12
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));

 

這種模式也許是最沒有擴展性的選項了。它雖然看上去讓代碼更簡潔,但是帶來的是擴展性的損失。我們上面的代碼中,所有的屬性和方法都不是副本,他們以“一個對象2個引用”的方式存在。修改其中一個都會影響到另外一個。

跨文件私有狀態(Cross-File Private State)

將組件分割到多個文件中的另外一個限制,就是每個文件只能訪問他自己的私有狀態,而沒有權限訪問其他文件的私有數據。這個問題可以解決,下面是一個“鬆耦合增益模式”的跨文件訪問私有狀態的版本:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;               
},               
_unseal = my._unseal = my._unseal || function () {                       
my._private = _private;                      
 my._seal = _seal;                       
my._unseal = _unseal;              
 };               
// permanent access to _private, _seal, and _unseal               
return my;
}(MODULE || {}));

現在任何文件都可以在本地變量_private中設置屬性,並且會立即將設置結果反饋給其他文件。一旦組件加載完成,應用程序應該調用 MODULE._seal()方法,這個方法會阻止外部程序訪問內部屬性_private。如果這個組件被其他文件進行了擴展(增益),則可在加載新的文件之前,調用_unseal()方法解封,然後在文件加載完之後調用_seal()再次將類對外部屏蔽。

譯者注:注意_unseal方法會在對象裏添加1個公開的屬性和2個公開方法,其他文件就可以在my對象中隊private屬性進行擴展,然後利用_seal()方法刪除公開的屬性,保持對象的密封性)

這個模式是今天我在工作的時候想到的,我沒有在其他地方見過這種用法,我認爲這是一種十分有用的模式。

子模塊(Sub-modules)

Our final advanced pattern is actually the simplest. There are many good cases for creating sub-modules. It is just like creating regular modules:

我們最後的一個高級模式實際上是最簡單的,創建子模塊有很多的好處,創建子模塊就和創建普通模塊一樣:

?
1
2
3
4
5
MODULE.sub = (function () {       
var my = {};      
 // ...               
return my;
}());

 

雖然這很簡單,但我還是認爲子模塊應該包含進來,因爲子模塊擁有所有正常模塊有的高級特性,包括增益和私有數據保存。

結論

大多數高級模式都可以結合起來使用,如果是我的話,我比較喜歡把“鬆耦合增益”、“私有狀態保存”、“子模式”結合起來使用

我這篇文字並沒有討論性能相關的內容,不過我想簡單的說一下:組件模式是對性能有好處的。因爲它確實讓下載快了很多:使用“鬆耦合增益”模式可以讓腳本並行的無阻塞加載,這加快了下載速度。整體初始化完成的時間也許比其他方法慢了一些,不過這是值得的。

作爲結尾,這裏有一個子模塊的例子,他動態的加載自己到它的父模塊中。這個模式允許整個模塊並行的加載自己,以及自己的子模塊。

?
1
2
3
4
5
6
7
8
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));

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章