深度剖析JavaScript模塊化模式

作者 ben cherry ,譯者 魏楷聰 發(fā)布于 2015年01月20日


The module pattern is a common JavaScript coding pattern. It’s generally well understood, but there are a number of advanced uses that have not gotten a lot of attention. In this article, I’ll review the basics and cover some truly remarkable advanced topics, including one which I think is original.

JavaScript的模塊化模式是一種常見(jiàn)的編碼模式。人們對(duì)它普遍有所了解,但一些高級(jí)的用法并沒(méi)有得到很大的關(guān)注。在這篇文章中,將會(huì)回顧基礎(chǔ)的以及涵蓋一些真正杰出的高級(jí)主題,而其中有一個(gè)我認(rèn)為是我自己獨(dú)創(chuàng)的。

The Basics

基礎(chǔ)部分

We’ll start out with a simple overview of the module pattern, which has been well-known since Eric Miraglia (of YUI) first blogged about it three years ago. If you’re already familiar with the module pattern, feel free to skip ahead to “Advanced Patterns”.

我們將開(kāi)始對(duì)模塊化模式做一個(gè)簡(jiǎn)單的概述,這個(gè)模式在三年前Eric Miraglia寫(xiě)了一個(gè)關(guān)于它的博客而被人們所了解。如果你已經(jīng)熟悉模塊化模式,隨時(shí)跳到“高級(jí)模式”吧。

This is the fundamental construct that makes it all possible, and really is the single best feature of JavaScript. We’ll simply create an anonymous function, and execute it immediately. All of the code that runs inside the function lives in a closure, which provides privacy and state throughout the lifetime of our application.

這個(gè)基本結(jié)構(gòu)讓一切成為可能,并且它確實(shí)是JavaScript中一項(xiàng)最好的特性。我們將簡(jiǎn)單地創(chuàng)建一個(gè)匿名函數(shù),并立即執(zhí)行它。所有在這個(gè)函數(shù)里執(zhí)行的代碼都位于閉包里,閉包提供的私有化和狀態(tài),貫穿于我們的應(yīng)用的生命周期。

(function () {

// ... all vars and functions are in this scope only

// still maintains access to all globals

}());

Notice the () around the anonymous function. This is required by the language, since statements that begin with the token function are always considered to be function declarations. Including () creates a function expression instead.

請(qǐng)注意:包圍匿名函數(shù)的()是JavaScript語(yǔ)言所要求的,因?yàn)橐赃@個(gè)標(biāo)記函數(shù)開(kāi)頭的語(yǔ)句總被認(rèn)為是函數(shù)聲明,包括用()創(chuàng)建一個(gè)函數(shù)表達(dá)式。

Global Import

全局導(dǎo)入

JavaScript has a feature known as implied globals. Whenever a name is used, the interpreter walks the scope chain backwards looking for a var statement for that name. If none is found, that variable is assumed to be global. If it’s used in an assignment, the global is created if it doesn’t already exist. This means that using or creating global variables in an anonymous closure is easy. Unfortunately, this leads to hard-to-manage code, as it’s not obvious (to humans) which variables are global in a given file. Luckily, our anonymous function provides an easy alternative. By passing globals as parameters to our anonymous function, we import them into our code, which is both clearer and faster than implied globals.

JavaScript有一項(xiàng)特性叫隱式全局變量。當(dāng)使用一個(gè)變量名時(shí),解釋器會(huì)反向沿著作用域鏈尋找這個(gè)變量名的var聲明。如果什么都沒(méi)找到,則假定變量是全局變量。如果一個(gè)變量并未聲明而直接為其賦值則會(huì)被創(chuàng)建為全局變量。這意味著在匿名閉包中使用或者創(chuàng)建全局變量是容易的。但不幸的是,由于在給定的文件里全局變量是不明顯的,導(dǎo)致難以管理代碼。而幸運(yùn)的是,匿名函數(shù)提供了一種簡(jiǎn)單的替代方法。通過(guò)將全局變量作為參數(shù)傳遞給匿名函數(shù),從而進(jìn)入到匿名函數(shù)里供調(diào)用,這樣比隱式全局變量更清晰、更快捷。

Here’s an example:

這里有一個(gè)例子:

(function ($, YAHOO) {

// now have access to globals jQuery (as $) and YAHOO in this code

}(jQuery, YAHOO));

Module Export

模塊化導(dǎo)入

Sometimes you don’t just want to use globals, but you want to declare them. We can easily do this by exporting them, using the anonymous function’s return value. Doing so will complete the basic module pattern, so here’s a complete example:

有時(shí)你不僅僅只是想使用全局變量,而想聲明它們。我們可以很容易地將它們傳遞給匿名函數(shù),并使用匿名函數(shù)的返回值。這樣將實(shí)現(xiàn)基本模塊模式,這里有一個(gè)完整的例子:

var MODULE = (function () {

var my = {},? privateVariable = 1;

function privateMethod() {

// ...

}

my.moduleProperty = 1;

my.moduleMethod = function () {

// ...

};

return my;

}());

Notice that we’ve declared a global module named MODULE, with two public properties: a method named MODULE.moduleMethod and a variable named MODULE.moduleProperty. In addition, it maintains private internal state using the closure of the anonymous function. Also, we can easily import needed globals, using the pattern we learned above.

請(qǐng)注意:我們已經(jīng)聲明了一個(gè)叫“MODULE”的全局模塊,帶有兩個(gè)公共屬性:一個(gè)叫“moduleMethod”的方法和一個(gè)叫“moduleProperty”的變量。此外,它通過(guò)匿名閉包維護(hù)了私有的內(nèi)部狀態(tài)。同時(shí),我們可以通過(guò)上面學(xué)習(xí)過(guò)的模式輕松地調(diào)用所需的全局變量。

Advanced Patterns

高級(jí)模式

While the above is enough for many uses, we can take this pattern farther and create some very powerful, extensible constructs. Lets work through them one-by-one, continuing with our module named MODULE.

雖然前面所提到的已經(jīng)夠用了,但我們可以更進(jìn)一步采取高級(jí)模式來(lái)創(chuàng)建一些非常強(qiáng)大的,可擴(kuò)展的結(jié)構(gòu)。讓我們繼續(xù)“MODULE”模塊,一步一步地完成。

Augmentation

增強(qiáng)

One limitation of the module pattern so far is that the entire module must be in one file. Anyone who has worked in a large code-base understands the value of splitting among multiple files. Luckily, we have a nice solution to augment modules. First, we import the module, then we add properties, then we export it. Here’s an example, augmenting our MODULE from above:

到目前為止,模塊模式的一個(gè)限制是整個(gè)模塊必須在同一個(gè)文件中。在一個(gè)龐大的代碼庫(kù)上編碼的開(kāi)發(fā)者,都明白代碼分割在不同的文件中的好處。幸運(yùn)的是,我們有一個(gè)很好的解決方案去增強(qiáng)模塊。首先,我們導(dǎo)入模塊,然后添加屬性,然后導(dǎo)出模塊。這里有一個(gè)增強(qiáng)我們之前的模塊的例子:

var MODULE = (function (my) {

my.anotherMethod = function () {

// added method...

};

return my;

}(MODULE));

We use the var keyword again for consistency, even though it’s not necessary. After this code has run, our module will have gained a new public method named MODULE.anotherMethod. This augmentation file will also maintain its own private internal state and imports.

盡管是非必要的,但為了確保一致性,我們?cè)俅问褂胿ar這個(gè)關(guān)鍵字。當(dāng)這段代碼運(yùn)行完,模塊將會(huì)獲得一個(gè)名叫“anotherMethod”的新的公有的方法。這個(gè)增強(qiáng)了的文件也會(huì)維護(hù)它的私有內(nèi)部狀態(tài)和傳遞給它的參數(shù)。

Loose Augmentation

松耦合的增強(qiáng)

While our example above requires our initial module creation to be first, and the augmentation to happen second, that isn’t always necessary. One of the best things a JavaScript application can do for performance is to load scripts asynchronously. We can create flexible multi-part modules that can load themselves in any order with loose augmentation. Each file should have the following structure:

在前面的例子中,首先我們創(chuàng)建模塊,然后擴(kuò)展,但那并不是必須的。提高JavaScript應(yīng)用的性能最好的方式之一是異步加載腳本。我們可以創(chuàng)建靈活的多模塊以便以任意的順序通過(guò)降低耦合來(lái)加載。每個(gè)文件都應(yīng)該具備以下結(jié)構(gòu):

var MODULE = (function (my) {

// add capabilities...

return my;

}(MODULE || {}));

In this pattern, the var statement is always necessary. Note that the import will create the module if it does not already exist. This means you can use a tool like LABjs and load all of your module files in parallel, without needing to block.

在這個(gè)模式中,var語(yǔ)句總是必須的。注意,如果模塊不存在,則導(dǎo)入將會(huì)創(chuàng)建模塊。這意味著你可以使用工具比如LABjs和并行加載所有的模塊文件,而無(wú)需塊。

Tight Augmentation

While loose augmentation is great, it does place some limitations on your module. Most importantly, you cannot override module properties safely. You also cannot use module properties from other files during initialization (but you can at run-time after intialization). Tight augmentation implies a set loading order, but allows overrides. Here is a simple example (augmenting our original MODULE):

松耦合的增強(qiáng)是好的,它會(huì)制約你的模塊。最重要的是,你不能安全地覆蓋模塊的屬性。你也沒(méi)法從其它文件在初始化期間使用模塊的屬性(但是可以在初始化之后的在運(yùn)行時(shí))。緊耦合的增強(qiáng)意味著一組加載順序,但允許覆蓋。這里有一個(gè)簡(jiǎn)單的例子(增強(qiáng)我們的原始模塊):

var MODULE = (function (my) {

var old_moduleMethod = my.moduleMethod;

my.moduleMethod = function () {

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

};

return my;

}(MODULE));

Here we’ve overridden MODULE.moduleMethod, but maintain a reference to the original method, if needed.

我們重寫(xiě)了MODULE.moduleMethod,但保持原方法的引用,如果需要的話。

Cloning and Inheritance

復(fù)制和繼承

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));

This pattern is perhaps the least flexible option. It does allow some neat compositions, but that comes at the expense of flexibility. As I’ve written it, properties which are objects or functions will not be duplicated, they will exist as one object with two references. Changing one will change the other. This could be fixed for objects with a recursive cloning process, but probably cannot be fixed for functions, except perhaps with eval. Nevertheless, I’ve included it for completeness.

這種模式也許是最靈活的選項(xiàng)。它允許一些整潔的成分,但是以犧牲靈活性為代價(jià)的。正如我編寫(xiě)的那樣,作為對(duì)象或函數(shù)的屬性將不會(huì)被復(fù)制,他們作為一個(gè)對(duì)象有兩個(gè)引用而存在。改變其中一個(gè)將會(huì)相應(yīng)地改變另一個(gè)。這是對(duì)遞歸對(duì)象復(fù)制處理的固定用法,但對(duì)于函數(shù)就可能不是固定的了,除非eval。不過(guò),我把它寫(xiě)上是為了完整性。

Cross-File Private State

跨越文件私有狀態(tài)

One severe limitation of splitting a module across multiple files is that each file maintains its own private state, and does not get access to the private state of the other files. This can be fixed. Here is an example of a loosely augmented module that will maintain private state across all augmentations:

把一個(gè)模塊放在多個(gè)文件里的一個(gè)限制是每個(gè)文件都得維護(hù)它的私有狀態(tài),而不訪問(wèn)其它文件的私有狀態(tài)。這個(gè)是固定的。這里有一個(gè)通過(guò)所有的增強(qiáng)來(lái)維護(hù)私有狀態(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;

};

// permanent access to _private, _seal, and _unseal

return my;

}(MODULE || {}));

Any file can set properties on their local variable _private, and it will be immediately available to the others. Once this module has loaded completely, the application should call MODULE._seal(), which will prevent external access to the internal _private. If this module were to be augmented again, further in the application’s lifetime, one of the internal methods, in any file, can call _unseal() before loading the new file, and call _seal() again after it has been executed. This pattern occurred to me today while I was at work, I have not seen this elsewhere. I think this is a very useful pattern, and would have been worth writing about all on its own.

任何文件都可以為它們的局部變量_private設(shè)置屬性,然后它將會(huì)立即生效。當(dāng)這個(gè)模塊完全加載完,這個(gè)應(yīng)用程序需應(yīng)該調(diào)用MODULE._seal()防止外部訪問(wèn)內(nèi)部變量_private。如果這個(gè)模塊再繼續(xù)增強(qiáng),進(jìn)一步在應(yīng)用程序的生命周期里,在任何文件里有一個(gè)內(nèi)部方法,在加載新的文件之前可以調(diào)用_unseal(),然后在執(zhí)行完后再調(diào)用_seal()。今天我在工作中看到這個(gè)模塊,我從未在其它地方見(jiàn)過(guò)這種模式。我認(rèn)為這是一種非常有用的模式,并且一直都值得寫(xiě)下來(lái)。

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:

我們最終的高級(jí)模式實(shí)際上是最簡(jiǎn)潔的。有很多好的情況下可以創(chuàng)建子模塊。它們就像創(chuàng)建常規(guī)模塊:

MODULE.sub = (function () {

var my = {};

// ...

return my;

}());

While this may have been obvious, I thought it worth including. Sub-modules have all the advanced capabilities of normal modules, including augmentation and private state.

雖然這可能是顯而易見(jiàn)的,但我認(rèn)為值得包括它。子模塊擁有正常模塊所有的高級(jí)功能,包括增強(qiáng)的和私有的狀態(tài)。

Conclusions

總結(jié)

Most of the advanced patterns can be combined with each other to create more useful patterns. If I had to advocate a route to take in designing a complex application, I’d combine loose augmentation, private state, and sub-modules.

最高級(jí)的模式可以互相結(jié)合創(chuàng)造出更多有用的模式。如果要我提倡一種設(shè)計(jì)復(fù)雜應(yīng)用程序的方式,我會(huì)把增強(qiáng)松耦合,私有狀態(tài)和子模塊結(jié)合起來(lái)。

I haven’t touched on performance here at all, but I’d like to put in one quick note: The module pattern is good for performance. It minifies really well, which makes downloading the code faster. Using loose augmentation allows easy non-blocking parallel downloads, which also speeds up download speeds. Initialization time is probably a bit slower than other methods, but worth the trade-off. Run-time performance should suffer no penalties so long as globals are imported correctly, and will probably gain speed in sub-modules by shortening the reference chain with local variables.

到目前還未涉及到性能問(wèn)題,但我在這里做一個(gè)速記:模塊化模式是有利于性能的。它縮減得非常好,這使得下載代碼變得更快。通過(guò)增強(qiáng)松散耦合,允許非阻塞并行下載,也會(huì)加快下載速度。初始化時(shí)間可能比其它方法稍微慢一些,但值得權(quán)衡。只要全局變量引用正確,運(yùn)行時(shí)的性能就不會(huì)有影響,而且很可能在子模塊中通過(guò)縮短局部變量的引用鏈?zhǔn)沟盟俣忍嵘?br>

To close, here’s an example of a sub-module that loads itself dynamically to its parent (creating it if it does not exist). I’ve left out private state for brevity, but including it would be simple. This code pattern allows an entire complex hierarchical code-base to be loaded completely in parallel with itself, sub-modules and all.

最后,一個(gè)例子:一個(gè)子模塊在父模塊中動(dòng)態(tài)加載自身(如果不存在的話,則創(chuàng)建它)。這種代碼模式允許一個(gè)完整的復(fù)雜的分層代碼庫(kù)通過(guò)并行的方式被完全加載,包括模塊自身和子模塊。

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 Module Pattern In-Depth?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評(píng)論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,324評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,018評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,675評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,417評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,783評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,960評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,522評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,267評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,471評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,698評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,099評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,386評(píng)論 1 294
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,204評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,436評(píng)論 2 378

推薦閱讀更多精彩內(nèi)容