JavaScript編程模式:模塊的力量
模塊模式是一個常用的JavaScript編程模式。它很好理解,但是還有一些高級的使用方法沒有引起廣泛的注意。如果你已經非常了解模塊模式,可以跳到"高級模式"的段落。
51CTO推薦閱讀:JavaScript中的函數式編程實踐
匿名閉包
匿名閉包是讓一切成為可能的基礎,而且這也是JavaScript最好的特性。我們創建個簡單的匿名函數看看。函數內運行的代碼都存在于閉包內,這個閉包在整個應用的生命周期內都保持私密和自己的狀態。(相關閱讀:揭開Javascript閉包的真實面目)
- (function () {
- // 所有的var和function都只存在于當前作用域內
- // 仍然可以讀取到所有的全局變量
- }());
注意:包住匿名函數的"()"。這是JavaScript語言本身的要求,因為由function開頭的代碼一律被識別為"函數聲明",用()包住則創建了一個函數表達式。
引用全局變量
JavaScript有個特質隱含的全局變量。無論一個name是否使用過,JavaScript解釋器反向遍歷作用域鏈查找這個name的var聲明,如果沒找到var,則這個對象是全局的。這意味著在一個匿名閉包中使用和創建全局變量很容易。不幸的是這讓代碼難與管理,對閱讀代碼的人來說很難區分哪些變量是全局的。幸好,我們的匿名函數提供了一個簡單的替代方案。將全局變量作為參數傳入匿名函數,這比用隱含全局變量更清晰更快速。例子:
- (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。除此之外,它用匿名函數的閉包保持自己的私有內部狀態。同時根據上一個例子,我們還可以很方便的引用全局變量。
高級模式
上面的內容對大多數用戶已經很足夠了,但我們還可以基于此模式發展出更強大,易于擴展的結構。
增生
模塊模式的一個限制是整個模塊必須寫在一個文件里。在大型編碼項目里工作的人都知道代碼分成多個文件的重要性。幸好,我們又一個很好的解決方案。首先,我們導入模塊,然后我們添加屬性,然后我們再把它導出。例子:
- var MODULE = (function (my) {
- my.anotherMethod = function () {
- //添加一些方法
- };
- return my;
- }(MODULE));
為確保一致性我們再次使用var關鍵字,盡管這不是必須的。代碼運行后,我們的模塊會獲得一個新的公有方法MODULE.anotherMethod。這個增生的文件也保持自己的私密性,內部狀態和對他的導入。
松散增生
我們的上一個例子要求我們的初始化模塊必須先運行。而增生必須第二步發生。這不應該是必要的。JavaScript的好處之一就是可以異步的讀取腳本文件。我們可以創建靈活的多塊的模塊,用Loose Augmentation,他們可以按任何順序加載自己。每個文件應該有如下的結構:
- var MODULE = (function (my) {
- // 添加一些功能
- return my;
- }(MODULE || {}));
在這個模式下,var聲明總是必須的注意如果模塊還不存在,導入就會新建模塊。這意味著你可以使用類似LABJavaScript這樣的工具并行的讀取所有你的模塊文件,沒有任何的阻塞。
#p#
緊密增生
雖然松散增生很牛叉,但是這對你的模塊有一定的限制。最重要的是你不能安全的重載(override)你的模塊屬性.你也不能在初始化的時候就使用模塊的屬性。緊密增生包含一個加載順序,但是允許重載(override).例子:
- var MODULE = (function (my) {
- var old_moduleMethod = my.moduleMethod;
- my.moduleMethod = function () {
- // 重載方法,可通過old_moduleMethod調用舊的方法
- };
- return my;
- }(MODULE));
這樣我們重載(override)了MODULE.moduleMethod,但如果需要,仍可保持對原方法的引用。
- Cloning and Inheritance 克隆和繼承
- 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 () {
- //在克隆里重載方法,通過super_moduleMethod接入父級(super)
- };
- return my;
- }(MODULE));
這個模式可能是最靈活的選擇。他允許一些neat compositions。這會帶來一些靈活性上的代價。
跨文件私有狀態
將一個模塊劃分到多個文件的限制之一是每個文件保持它自己的私有狀態,而且不能解接入其他文件的私有狀態。這是可以解決的,下面的例子用松散增生模塊在多個增生之間保持私有狀態:
- 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;
- };
- // 持久的接入 _private, _seal, 和 _unseal
- return my;
- }(MODULE || {}));
任何文件都可以對他們的局部變量_private設屬性,并且設置對其他的文件也立即生效。一旦這個模塊加載結束,應用會調用 MODULE._seal()"上鎖",這會阻止外部接入內部的_private。如果這個模塊需要再次增生,應用的生命周期內,任何文件都可以調用_unseal() ”開鎖”,然后再加載新文件。加載后再次調用 _seal()”上鎖”。
子模塊
我們最后的高級模式是最簡單的,有很多好例子來創建子模塊,就像創建一個普通的模塊:
- MODULE.sub = (function () {
- var my = {};
- // …
- return my;
- }());
盡管這很明顯,但我認為還是值得加進來的,子模塊具有一般模塊所有的高級能力,包括增生和私有狀態。
總結
大多數高級模式可以與其他的互相組合形成有用的模式。如果讓我來設計一個復雜應用的架構,我會組合使用loose augmentation, private state, 和 sub-modules.
這里我并沒有研究性能問題。但是我想順便提一句:模塊模式效率很好。代碼量可以更少,使加載代碼更快。使用 loose augmentation允許簡單的非阻礙式并行加載,這更可以提升下載的速度。初始化時間可能比其他方法要慢,但是這是值得的。
最后,這里有個sub-module動態的把自己加載到父模塊去(如果沒有則創建)。為了簡潔,這里沒有包含private state。這段代碼允許一個復雜的大型分層代碼庫并行的加載自己和它的子模塊:
- var UTIL = (function (parent, $) {
- var my = parent.ajax = parent.ajax || {};
- my.get = function (url, params, callback) {
- // ok, so I’m cheating a bit
- return $.getJavaScriptON(url, params, callback);
- };
- // etc…
- return parent;
- }(UTIL || {}, jQuery));
原文鏈接:http://www.douban.com/group/topic/10456277/
【編輯推薦】