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