本文譯自Ben Cherry的《JavaScript Module Pattern: In-Depth》。雖然我的不太認同js中私有變量存在的必要性,可是本文很是全面地介紹了Javascript中模塊化模式地方方面面。我讀完以後仍是受益不淺,因此翻譯出來但願對各位也有些幫助。html
模塊化編程是一種很是常見Javascript編程模式。它通常來講可使得代碼更易於理解,可是有許多優秀的實踐尚未廣爲人知。在這篇文章中,我將會回顧一下js模塊化編程的基礎,而且將會講到一些真的很是值得一提的進階話題,包括一個我認爲是我自創的模式。
ajax
咱們首先簡單地概述一下,自從三年前Eric Miraglia(YUI的開發者)第一次發表博客描述模塊化模式以來的一些模塊化模式。若是你已經對於這些模塊化模式很是熟悉了,大能夠直接跳過本節,從「進階模式」開始閱讀。編程
這是一種讓一切變爲可能的基本結構,同時它也是Javascript最棒的特性。咱們將簡單地建立一個匿名函數並當即執行它。全部的代碼將跑在這個函數內,生存在一個提供私有化的閉包中,它足以使得這些閉包中的變量可以貫穿咱們的應用的整個生命週期。安全
(function () { // ... all vars and functions are in this scope only // still maintains access to all globals }());
注意這對包裹匿名函數的最外層括號。由於Javascript的語言特性,這對括號是必須的。在js中由關鍵詞function開頭的語句老是會被認爲是函數聲明式。把這段代碼包裹在括號中就可讓解釋器知道這是個函數表達式。閉包
Javascript有一個特性叫作隱式全局變量。不管一個變量名在哪兒被用到了,解釋器會根據做用域鏈來反向找到這個變量的var
聲明語句。若是沒有找到var
聲明語句,那麼這個變量就會被視爲全局變量。若是這個變量用在一句賦值語句中,同時這個變量又不存在時,就會建立出一個全局變量。這意味着在匿名閉包中使用或建立全局變量是很容易的。不幸的是,這會致使寫出的代碼極難維護,由於對於人的直觀感覺來講,一眼根本分不清那些是全局的變量。異步
幸運的是,咱們的匿名函數提供了簡單的變通方法。只要將全局變量做爲參數傳遞到咱們的匿名函數中,就能夠獲得比隱式全局變量更清晰又快速的代碼了。下面是示例:ide
(function ($, YAHOO) { // now have access to globals jQuery (as $) and YAHOO in this code }(jQuery, YAHOO));
有時你不只想要使用全局變量,你還想要聲明它們,以供反覆使用。咱們能夠很容易地經過導出它們來作到這一點——經過匿名函數的返回值。這樣作將會完成一個基本的模塊化模式雛形,接下來會是一個完整的例子:模塊化
var MODULE = (function () { var my = {}, privateVariable = 1; function privateMethod() { // ... } my.moduleProperty = 1; my.moduleMethod = function () { // ... }; return my; }());
注意咱們已經聲明瞭一個叫作MODULE
的全局模塊,它擁有2個公有的屬性:一個叫作MODULE.moduleMethod
的方法和一個叫作MODULE.moduleProperty
的變量。另外,它還維護了一個利用匿名函數閉包的、私有的內置狀態。同時,咱們能夠很容易地導入須要的全局變量,並像以前咱們所學到的那樣來使用這個模塊化模式。
函數
上面一節所描述的基礎已經足以應對許多狀況,如今咱們能夠將這個模塊化模式進一步的發展,建立更多強大的、可擴展的結構。讓咱們從MODULE
模塊開始,一一介紹這些進階模式。工具
整個模塊必須在一個文件中是模塊化模式的一個限制。任何一個參與大型項目的人都會明白將js拆分多個文件的價值。幸運的是,咱們擁有一個很棒的實現來放大模塊。首先,咱們導入一個模塊,併爲它添加屬性,最後再導出它。下面是一個例子——從本來的MODULE
中放大它:
var MODULE = (function (my) { my.anotherMethod = function () { // added method... }; return my; }(MODULE));
咱們用var關鍵詞來保證一致性,雖然它在此處不是必須的。在這段代碼執行完以後,咱們的模塊就已經擁有了一個新的、叫作MODULE.anotherMethod
的公有方法。這個放大文件也會維護它本身的私有內置狀態和導入的對象。
咱們的上面例子須要咱們的初始化模塊最早被執行,而後放大模塊才能執行,固然有時這可能也不必定是必需的。Javascript應用能夠作到的、用來提高性能的、最棒的事之一就是異步執行腳本。咱們能夠建立靈活的多部分模塊並經過寬放大模式使它們能夠以任意順序加載。每個文件都須要按下面的結構組織:
var MODULE = (function (my) { // add capabilities... return my; }(MODULE || {}));
在這個模式中,var
表達式使必需的。注意若是MODULE還未初始化過,這句導入語句會建立MODULE
。這意味着你能夠用一個像LABjs的工具來並行加載你全部的模塊文件,而不會被阻塞。
寬放大模式很是不錯,但它也會給你的模塊帶來一些限制。最重要的是,你不能安全地覆蓋模塊的屬性。你也沒法在初始化的時候,使用其餘文件中的屬性(但你能夠在運行的時候用)。緊放大模式包含了一個加載的順序序列,而且容許覆蓋屬性。這兒是一個簡單的例子(放大咱們的原始MODULE
):
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
的實現,但在須要的時候,能夠維護一個對原來方法的引用。
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));
這個模式多是最缺少靈活性的一種選擇了。它確實使得代碼顯得很整潔,但那是用靈活性的代價換來的。正如我上面寫的這段代碼,若是某個屬性是對象或者函數,它將不會被複制,而是會成爲這個對象或函數的第二個引用。修改了其中的某一個就會同時修改另外一個(譯者注:由於它們根本就是一個啊!)。這能夠經過遞歸克隆過程來解決這個對象克隆問題,但函數克隆可能沒法解決,也許用eval能夠解決吧。所以,我在這篇文章中講述這個方法僅僅是考慮到文章的完整性。
把一個模塊分到多個文件中有一個重大的限制:每個文件都維護了各自的私有變量,而且沒法訪問到其餘文件的私有變量。但這個問題是能夠解決的。這裏有一個維護跨文件私有變量的、寬放大模塊的例子:
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()
。我現在在工做中使用這種模式,並且我在其餘地方尚未見過這種方法。我以爲這是一種很是有用的模式,很值得就這個模式自己寫一篇文章。
咱們的最後一種進階模式是顯而易見最簡單的。建立子模塊有許多優秀的實例。這就像是建立通常的模塊同樣:
MODULE.sub = (function () { var my = {}; // ... return my; }());
雖然這看上去很簡單,但我以爲仍是值得在這裏提一提。子模塊擁有一切通常模塊的進階優點,包括了放大模式和私有化狀態。
大多數進階模式能夠結合到一塊兒來建立一個更爲有用的模式。若是實在要我推薦一種設計複雜應用程序的模塊化模式的化,我會選擇結合寬放大模式、私有變量和子模塊。
我還未考慮過這些模式的性能問題,但我寧願把這轉化爲一個更簡單的思考方式:若是一個模塊化模式有很好的性能,那麼它可以把最小化作的很棒,使得下載這個腳本文件更快。使用寬放大模式能夠容許簡單的非阻塞並行下載,這就會加快下載速度。初始化時間可能會稍慢於其餘方法,但權衡利弊後這仍是值得的。只要全局變量導入準確,運行時性能應該會不會受到影響,並且還有可能在子模塊中經過用私有變量縮短引用鏈來獲得更快的運行速度。
做爲結束,這裏是一個子模塊動態地把自身加載到它的父模塊的例子(若是父模塊不存在則建立它)。爲了簡潔,我把私有變量給去除了,固然加上私有變量也是很簡單的啦。這種編程模式容許一整個複雜層級結構代碼庫經過子模塊並行地完成加載。
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吧!