對於那些正在構建大型應用程序,而對JavaScript不甚瞭解的開發者而言,他們最初必需要面對的挑戰之一就是如何着手組織代碼。起初只要在<script>
標記之間嵌入幾百行代碼就能跑起來,不過很快代碼就會變得一塌糊塗。而問題是,JavaScript沒有爲組織代碼提供任何明顯幫助。從字面上看,C#有using
,Java有import
——而JavaScript一無全部。這迫使JavaScript編寫者試驗不一樣的約定,並使用現有的語言建立了一些切實可行的方法來組織大型JavaScript應用程序。javascript
各類模式(patterns)、工具(tools)及慣例(practices)會造成現代JavaScript的基礎,它們必未來自於語言自己實現以外。php
——Rebecca Murphyhtml
用於解決組織代碼問題、使用最爲普遍的方法之一是模塊模式(Module Pattern)。我嘗試在下面解釋一個基本示例,並討論其若干特性。要想閱讀更精彩的說明,並瞭解用盡各類不一樣方法的怪人,那麼請參閱Ben Cherry的帖子——JavaScript Module Pattern: In-Depth(深刻理解JavaScript模塊模式)。前端
(function(lab49) { function privateAdder(n1, n2) { return n1 + n2; } lab49.add = function(n1, n2) { return privateAdder(n1, n2); };})(window.lab49 = window.lab49 || {});
在上例中,咱們使用了一些來自語言的基本功能,從而創造出在C#及Java等語言中見過的相似結構。java
請注意,這段代碼包在被當即調用的函數裏(仔細看最後一行)。因爲在瀏覽器中,默認狀況下會把JavaScript文件置於全局做用域級別上進行計算(evaluated),所以在咱們在文件中聲明的任何內容都是隨處可用的。想象一下,要是先在lib1.js中聲明瞭var name = '...'
,而後又在lib2.js聲明瞭var name = '...'
。那麼後一句var聲明就會替掉前一句的值——這可不太妙。然而,因爲JavaScript擁有函數做用域級別,上例中所聲明的一切都位於函數自身做用域內,與全局做用域毫無瓜葛。這意味着,不管系統未來如何變化,位於函數中的任何內容都不會受到影響。node
在最後一行代碼中會看到,咱們要麼將window.lab49
賦給其自身,要麼將空對象{}
賦給它。儘管看起來有點兒怪,不過讓咱們一塊兒來看下這樣一個虛構系統,系統中的那些js文件一概使用了上例中的函數包裝器(function wrapper)。git
首個被引入的文件會計算那個或語句(...||...
),並發現左側的表達式undefined(未定義)。因爲undefined會被斷定爲假,所以或語句會進一步計算右側表達式,在本例中就是空對象。或語句其實是個表達式,它會返回計算結果,進而將結果賦給全局變量window.lab49
。github
如今輪到接下來的文件使用此模式了,它會執行或語句,並發現window.lab49
目前已經是對象實例——真(對象實例會被斷定爲真)。此時或語句會走捷徑,並返回這個會當即賦給其自身的值——其實什麼都沒作。api
由此致使的結果是,首個被引入的文件會建立lab49
命名空間(就是個JavaScript對象),並且全部使用這種結構的後續文件都只是重用此現有實例。數組
正如剛纔所說,因爲位於函數內部,在其內部聲明的全部內容都處於該函數的做用域內,而非全局做用域。這對於隔離代碼真是棒極了,不過它還帶來了一種效果,那就是沒人能調用它。真是中看不中用啊!
剛剛還談到,建立window.lab49
對象是爲了用命名空間來有效地管理咱們的內容。並且因爲變量lab49
被附加到window
對象上,所以它是全局可用的。爲了把其中的內容公佈給模塊外部,或許有人會公開聲稱,咱們要作的所有就是把一些值附加到那個全局變量上。正如上例中所寫的add
函數同樣。如今,在模塊外部就能夠經過lab49.add(2, 2)
來調用add
函數了。
在此函數中聲明一些值的另外一結果是,要是某個值沒有經過將其附加到全局命名空間或者此模塊外部的某個對象上的方式來顯示公開,那麼外部代碼就訪問不到該值。實際上,咱們剛好建立了一些私有值。
CommonJS是個社團,主要由服務器端JavaScript運行庫(server-side JavaScript runtimes)編寫者組成,他們致力於將模塊的公開及訪問標準化的工做。值得注意的是,他們提議的模塊系統並不是標準,由於它不是出自制定JavaScript標準的同一社團,因此它更像是服務器端JavaScript運行庫編寫者彼此之間的非正式約定。
我一般會支持CommonJS的想法,但要搞清楚的是:它並非一份崇高而神聖的規範(就像ES5同樣);它只不過是一些人在郵件列表中所討論的想法。並且多數想法都未付諸實現。
——Ryan Dahl, node.js的創造者
這份模塊規範的核心至關直截了當。全部模塊都要在其自身的上下文中進行計算,而且要有個全局變量exports
供模塊使用。而全局變量exports
只是個普通的JavaScript對象,甚至能夠自行往上面附加內容,它與上面展現的命名空間對象(lab49
)相似。要想訪問某個模塊,需調用全局函數require
,並指明所請求的包標識符。接着會計算此模塊,並且不管返回何值都會將其附加到exports
上。而後會緩存此模塊,以便後來的require
函數調用。
// calculator.js// 計算器模塊——譯註exports.add = function(n1, n2) {};// app.js// 某個須要調用計算器模塊的應用程序。// './calculator'即包標識符。——譯註var calculator = require('./calculator');calculator.add(2, 2);
要是擺弄過Node.js,或許會對以上代碼有種似曾相識的感受。這種用Node來實現CommonJS模塊的方式真是出奇地簡單,在node-inspector(一款Node調試器)中查看模塊時,會顯示其包裝在函數內部的內容,這些內容正是傳遞給exports
及require
的值。很是相似於上面展現的手卷模塊內容。
有幾個node項目(Stitch及Browserify),它們將CommonJS模塊帶進了瀏覽器。服務器端組件會把這些彼此獨立的模塊js文件打包到單獨的js文件中,並把那些代碼用生成的模塊包裝器包起來。
CommonJS主要是爲服務器端JavaScript運行庫設計的,並且因爲有幾個屬性使得它們難以在瀏覽器中組織客戶端代碼。
require
必須當即返回——要是已經擁有全部內容時這會工做得很好,不過這致使難以使用腳本加載器(script loader)去異步下載腳本。
每一個模塊佔一個文件——爲了合併爲CommonJS模塊,必須把它們以某種風格組織起來,幷包裹到一個函數中。要是沒有相似於上面所說起的服務器組件,那麼就難以使用它們,而且在許多環境(ASP.NET,Java)下這些服務器組件尚不存在。
異步模塊定義(Asynchronous Module Definition,一般稱爲AMD)已被設計爲適合於瀏覽器的模塊格式。它起初源於CommonJS社團的提案,不過自從遷移到GitHub上之後,現已加入了配套的測試套件,以便模塊系統編寫者來驗證其代碼是否符合AMD的API。
AMD的核心是define
函數。調用define
函數最多見的方式是傳入三個參數——模塊名(也就是說再也不與文件名綁定)、該模塊依賴的模塊標識符數組、以及將會返回該模塊定義的工廠函數。(調用define
函數的其餘方式——詳細信息請參閱AMD wiki)。
// 定義calculator(計算器)模塊。——譯註define('calculator', ['adder'], function(adder) { // 返回具備add方法的匿名對象。——譯註 return { add: function(n1, n2) { /* * 實際調用的是adder(加法器)模塊的add方法。 * 並且adder模塊已在前一參數['adder']中指明瞭。——譯註 */ return adder.add(n1, n2); } };});
因爲此模塊的定義包在define
函數的調用中,所以這意味着能夠欣然將多個模塊都放在單個js文件中。此外,因爲當調用模塊工廠函數define
時,模塊加載器已擁有控制權,所以它能夠自行安排時間去解決(模塊間的)依賴關係——對於那些須要先異步下載的模塊,真可謂駕輕就熟。
爲了與原先的CommonJS模塊提案保持兼容已作出了巨大的努力。有些特殊行爲是爲了能在模塊工廠函數中使用require
及exports
,這意味着,那些傳統的CommonJS模塊可直接拿來用。
看起來AMD正在成爲頗受歡迎的組織客戶端JavaScript應用程序的方式。不管是如RequireJS或curl.js等模塊資源加載器,仍是像Dojo等最近已支持AMD的JavaScript應用程序,狀況都是如此。
缺少語言級別的結構,而沒法將代碼組織到模塊中,這可能會讓來自其餘語言的開發者以爲很不爽。然而,正由於此缺陷才迫使JavaScript開發者想出他們本身的模塊組織模式,並且咱們已經可以隨着JavaScript應用程序的發展進行迭代並改進。欲深刻了解此主題請訪問Tagneto的博客。
想象一下,即使在10年前就已將此類功能引入語言。那麼他們也不可能想到後來的那些需求,例如在服務器上運行大型JavaScript應用程序、在瀏覽器中異步加載資源、或者像text templates(文本模板)(就是些文本加載器,其功能相似於RequireJS)那樣引入資源等等。
正在考慮將模塊(Modules)做爲Harmony/ECMAScript 6的語言級別功能。這多虧了模塊系統編寫者們的奇思妙想、以及過去數年中所作的辛勤工做,更有可能的是,咱們最終將獲得適合於構建現代JavaScript應用程序的語言。
查看英文原文:JavaScript Modules
你們好,我是David Padbury。我在位於紐約的Lab49公司從事爲金融行業建立高級應用程序的工做。我不只把大部分時間花在開發複雜的HMTL5及JavaScript前端系統上,並且還經常會涉獵Java、.NET、以及其餘企業類型的內容。
在一些用戶組及會議上,我談到過許多與HTML5及JavaScript有關的內容,偶爾也會說起node.js。目前,我致力於幫助那些熟悉更爲傳統的胖客戶端技術(例如WPF、Silverlight、Flex、或Swing)的開發者,以便他們理解如何使用HTML5來構建相似的應用程序。要是您正在圍繞這些主題尋找演講者,那麼請聯繫我。
在此發佈內容僅表明我的觀點,與個人老闆無關。