JavaScript module pattern是一種常見的javascript編碼模式。這種模式自己很好理解,可是有不少高級用法尚未獲得你們的注意。本文,咱們將回顧這種設計模式,而且介紹一些高級的用法,其中一個是我原創的。javascript
在個人項目中常常會在一個jsp中import包含下面這樣的JavaScript代碼的文件:java
var myBrand = { name:"xxx" }; var isBrand = function(brand) { return brand === "xxx" }
在和咱們公司一位很是senior的同事陳顯軍結對編程的時候,得知:定義在全部函數最外邊,使用或不使用var關鍵字定義的變量都是全局變量。並且最終合併成一個js文件,也就是說這種方式定義方法和變量是很是危險並且很是容易污染一些其餘框架中的全局變量。這也就是爲何我會翻譯這篇JavaScript module pattern。一些經常使用的框架像JQuery和Underscore都是採用這種模塊化的方式來實現的。編程
咱們會先從介紹module模式,若是你對module模式比較熟悉,能夠直接跳到高級用法處。設計模式
這是一種基本的javascipt構造方法,也是javascipt中最好的特性,咱們申明一個匿名函數,而且當即執行這個函數。函數中全部的代碼都生活在一個閉包裏,這個閉包讓私有方法和成員成爲現實,而且僅可以存在咱們應用程序的整個生命週期。閉包
(function () { // ... all vars and functions are in this scope only // still maintains access to all globals }());
請注意()包着的這個匿名函數。這是語言須要,若是沒有()包着函數,javasript會認爲這是一個函數聲明。因爲括弧()和JS的&&,異或,逗號等操做符是在函數表達式和函數聲明上消除歧義的,因此一旦解釋器知道其中一個是表達式,其餘的也都默認成爲了表達式。框架
javascipt有一種特性叫隱式全局變量。就是當有一個變量被使用時,解釋器會遍歷做用域鏈來尋找var聲明的這個變量,若是沒有找到,這個變量就是默認成爲了隱式全局變量。同時這也說明了在匿名閉包中建立全局變量也是很是容易的。不幸的是這樣的作法讓代碼變得很難管理,由於咱們很難知道哪一個變量的聲明週期是全局的。異步
幸運的是,咱們的匿名函數提供了一個簡單的選擇。經過全局變量做爲參數,經過這種方式引入全局變量比咱們本身定義的隱式全局變量而言,更清晰更快速。這有一個例子:jsp
(function ($, YAHOO) { // now have access to globals jQuery (as $) and YAHOO in this code }(jQuery, YAHOO));
如今不少類庫裏都有這種使用方式,好比jQuery源碼。ide
有時候,若是你不只想傳入全局變量 ,並且你想聲明一些全局變量,咱們這裏提供了一個簡單的方法來導出他們,而這個方法就是匿名函數的返回值。這樣作就是最基本的模塊模式。示例以下:模塊化
var MODULE = (function () { var my = {}, privateVariable = 1; function privateMethod() { // ... } my.moduleProperty = 1; my.moduleMethod = function () { // ... }; return my; }());
請注意,咱們聲明瞭一個全局的模塊叫MODULE,它包含兩個屬性,一個成員變量moduleProperty和一個成員方法moduleMethod。除此以外,它還使用匿名函數的閉包維護了私有內部狀態,咱們也能夠經過第二條規則輕鬆的傳入咱們須要傳入的全局變量。
上面的功能對咱們平常開發來講已經很實用了,可是咱們仍是基於此模式能夠設計出更強大,更易於拓展的結構。
對於模塊模式而言,惟一一條限制就是咱們的模塊的代碼都只能在一個文件中聲明。任何在大項目中幹過得人都體會到分文件的好處。幸運的是咱們有一個很是好的辦法來解決這一瓶頸。
首先咱們導入模塊,而後添加屬性,隨後將其導出,以下面這個例子:
var MODULE = (function (my) { my.anotherMethod = function () { // added method... }; return my; }(MODULE));
這裏咱們仍是用var來保存這個返回值,雖說這裏不是必要。等解釋玩段代碼,新的方法會添加到MODULE中,這個拓文件中依然能夠包含一些私有的方法和屬性。
咱們上面這個例子要求咱們的模塊被定義過,而後纔有擴展。但這不老是必須的。對JavaScript而言,提升性能最好的方式就是異步加載js文件,咱們在加載多個文件的時候但願可以解決加載順序的問題,咱們採用下面這種方式來聲明全部的相同的模塊:
var MODULE = (function (my) { // add capabilities... return my; }(MODULE || {}));//這是確保MODULE對象,在存在的時候直接用,不存在的時候直接賦值爲{},
在這種模式下,用var聲明每一個模塊是必須的,否則其餘文件沒法讀取不到這個模塊。
雖說鬆耦合擴展很棒,可以幫你分文件定義,可是他不能幫你實現重載的功能,也不能在模塊初始化的時候使用模塊的屬性。緊模式幫你拓展了加載順序限制,還提供了重載機制:
var MODULE = (function (my) { var old_moduleMethod = my.moduleMethod; my.moduleMethod = function () { // method override, has access to old through old_moduleMethod... }; return my; }(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));
這種模式也許是最不易拓展的的選擇了。雖然這種設計十分靈巧,可是也付出了巨大代價。正如我所寫的,模塊的方法或屬性沒有被複制,而是對同一個對象多了一個引用而已。改變一個模塊的實現,也會影響到另外一個。若是用遞歸來實現克隆能夠解決這一問題,但遞歸對function函數的賦值也很差用,因此咱們在遞歸的時候eval相應的function。無論怎樣仍是告訴你們這一點。
咱們最後一個模式是這幾個當中最簡單的模式,咱們有不少場景會用到它:
MODULE.sub = (function () { var my = {}; // ... return my; }());
這其實也是module pattern中的高級用法之一,同時,咱們也能夠將上面的一些模式應用在子模塊上。
大多數高級用法能夠和其餘設計模式結合其餘使用,若是對於設計複雜的項目,我我的傾向使用:鬆耦合擴展,私有狀態和子模塊三種模式。在這裏我徹底沒有提到性能,但這裏我仍是提一下:這些模式對性能都有提高,鬆耦合容許並行下載,提升下載速度。代碼初始化可能會比原來慢一點,可是因爲全局變量的減小,子模塊在獲取局部變量的速度鏈變短,全部運行時JavaScript速度是會有顯著提高。
(原文中高級模式中的多文件訪問私有成員還未能理解,因此沒有翻譯,望請諒解)
本文翻譯自:http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth