閱讀483 返回首頁    go 阿裏雲 go 技術社區[雲棲]


AMD終極揭秘

原文: https://www.sitepen.com/blog/2012/06/25/amd-the-definitive-source/

作者:Kris Zyp

譯者:Elaine Liu

究竟什麼是AMD?

隨著web應用不斷發展和對JavaScript依賴的進一步加深,出現了使用模塊(Modules)來組織代碼和依賴性。模塊使得我們創建明確清晰的組件和接口,這些組件和接口能夠很容易的加載並連接到其依賴組件。 AMD模塊係統提供了使用JavaScript模塊來構建Web應用的完美方式,並且這種方式具有形式簡單,異步加載和廣泛采用的特點。

異步模塊定義(AMD)格式是一套API,它用於定義可重用的並能在多種框架使用的模塊。開發AMD是為了提供一種定義模塊的方式,這種方式可以使用原生的瀏覽器腳本元素機製來實現模塊的異步加載。AMD API由2009年Dojo 社區的討論中產生,然後移動到討論CommonJS如何更好的為瀏覽器適應CommonJS模塊格式(被NodeJS使用)。 CommonJS已經發展成為單獨的一個標準並有其專門的社區。AMD已經廣泛普及,形成了眾多模塊加載實現並被廣泛使用。在SitePen公司,我們廣泛的使用Dojo的AMD機製工作,為其提供支持,並積極的建設這一機製。

本文中用到的一些重要詞匯

  • 模塊(module) —— 一個經過封裝的JavaScript文件,它遵循模塊的格式,指定依賴和提供模塊輸出。
  • 模塊標識(module ID)——唯一標識模塊的字符串,相對模塊標識將根據當前模塊的標識解釋為絕對模塊標識
  • 模塊路徑 (module path)——用於檢索模塊的URL。一個模塊標識對應於一個模塊路徑,該路徑是由加載器配置規則設定的(缺省情況下,模塊路徑假定為該模塊對於根路徑的相對路徑,根路徑通常是模塊加載器包所在的父目錄)。
  • 模塊加載器(module loader)——解析和加載模塊以及相關依賴的JavaScript代碼,它與插件交互,並處理加載配置。
  • 包(package)——一組模塊集合。例如dojo,dijit以及dgrid都是包。
  • 構建器(builder)——用於將模塊(或者多個模塊)以及其依賴連接在一起產生單個JavaScript文件的工具,這樣使得一個應用程序能夠包含多個模塊,並能創建多個構建層次,從而使得它們在被加載時實現HTTP請求數目最小化。
  • 層(layer)——一個文件,它包含若幹模塊並由構建器優化生成單個文件。
  • 依賴(dependency)——為了使另一個模塊正常工作而必須加載的模塊。
  • AMD——異步模塊定義,一種為瀏覽器開發提供最優體驗的模塊定義格式。
  • 工廠方法(factory)——通過define定義的並提供給模塊加載器的函數,它在所有依賴加載完後執行一次。

為什麼需要AMD模塊?

模塊化係統的基礎前提是:

  • 允許創建被封裝的代碼片段,也就是所謂的模塊
  • 定義本模塊與其他模塊之間的依賴
  • 定義可以被其他模塊使用的輸出的功能
  • 謹慎的使用這些模塊提供的功能
AMD滿足以上需求,並將依賴模塊設置為其回調函數的參數從而實現在模塊代碼被執行前異步的加載這些依賴模塊。AMD還提供了加載非AMD資源的插件係統。
雖然有其他的load JavaScript的替代方法,但使用腳本元素來加載JavaScript有特有的優勢,包括性能,減少調試(尤其在一些老版本的瀏覽器上)以及跨域的支持。因此AMD致力於提供基於瀏覽器開發的最優體驗。

AMD格式提供了幾個關鍵的好處。首先,它提供了一種緊湊的聲明依賴的方式。通過簡單的字符串數組來定義模塊依賴,使得開發者能夠花很小的代價輕鬆列舉大量模塊依賴性。

AMD幫助消除對全局變量的需求。 每個模塊都通過局部變量引用或者返回對象來定義其依賴模塊以及輸出功能。因此,模塊不需要引入全局變量就能夠定義其功能並實現與其他模塊的交互。AMD同時是“匿名的”,意味著模塊不需要硬編碼指向其路徑的引用, 模塊名僅依賴其文件名和目錄路徑,極大的降低了重構的工作量。

通過將依賴性映射為局部變量, AMD鼓勵高效能的編碼實踐。如果沒有AMD模塊加載器,傳統的JavaScript代碼必須依賴層層嵌套的對象來“命名”給定的腳本或者模塊。如果使用這種方式,通常需要通過一組屬性來訪問某個功能,這會造成全局變量的查找和眾多屬性的查找,增加了額外的開發工作同時降低了程序的性能。通過將模塊依賴性映射為局部變量,隻需要一個簡單的局部變量就能訪問某個功能,這是極其快速的並且能夠被JavaScript引擎優化。

使用AMD

最基礎的AMD API是define()方法,用於定義一個模塊及其依賴。通常我們這樣來寫一個模塊:

[javascript] view plaincopy
  1. define(dependencyIds, function(dependency1, dependency2,...){  
  2.    // module code  
  3. });  

dependencyIds 參數是一個字符串數組,用於表示需要加載的依賴模塊。這些依賴模塊將會被加載和執行。一旦所有依賴都被執行完畢,它們的輸出將作為參數提供給回調函數(define()方法的第二個參數)

為了展示AMD的基礎用法,我們可以定義一個使用dojo/query(css選擇器查詢)和dojo/on(事件處理)的模塊。

[javascript] view plaincopy
  1. define(["dojo/query""dojo/on"],  
  2.         function(query, on){  
  3.     return {  
  4.         flashHeaderOnClick: function(button){  
  5.             on(button, "click"function(){  
  6.                 query(".header").style("color""red");  
  7.             });  
  8.         }  
  9.     };  
  10. });  

一旦dojo/query和dojo/on被加載(當然也必須等到它們本事的依賴也被加載,以此類推), 回調函數將被調用,同時dojo/query的輸出(一個負責CSS選擇器查詢的函數)作為參數query,dojo/on的輸出(一個可以添加事件監聽器的函數)作為參數on被傳到這個回調函數中。回調函數(通常認為是模塊的工廠方法)被保證隻調用一次。

列在依賴集合中的每個模塊標識是一個抽象的模塊路徑。說它是抽象的因為它被模塊加載器轉移成真正的URL。正如你所見,模塊路徑並不需要包含“.js”後綴,這個後綴在加載的時候會自動添加。當模塊標識直接由模塊名打頭時,該名稱是模塊的絕對標識。相比之下,我們也可以通過由“./”或者"../"打頭表示當前目錄或者父目錄來指定相對標識。這些相對標識會通過標準路徑解析規則來解析成絕對標識。你可以定義一個模塊路徑規則來決定這些模塊路徑將如何轉換成URL。缺省情況下,模塊根目錄定義為相對於模塊加載器包的父目錄的路徑。例如,如果我們用下麵的方法加載Dojo(注意在這裏我們設置async屬性為true來保證異步AMD加載)

[javascript] view plaincopy
  1. <script src="/path/to/dojo/dojo.js" data-dojo-config="async:true"></script>  

那麼,假設根目錄到模塊的路徑為“/path/to/”。如果我們指定依賴於“my/module”,這個依賴將被解析為“/path/to/my/module.js”.

初始模塊加載

我們已經描述了如何創建一個簡單的模塊。然而,我們還需要一個入口來觸發這些依賴鏈。我們可以通過使用require() API來做到這一點。這個函數簽名基本跟define()一致,區別在於它用於加載依賴但而不需要定義一個模塊(當一個模塊被定義時,如果它不被別的模塊請求它是不會執行的)我們可以像下麵這樣加載我們的應用程序:

[html] view plaincopy
  1. <script src="/path/to/dojo/dojo.js"><!--mce:1--></script>  
  2. <script type="text/javascript"><!--mce:2--></script>  

Dojo提供了加載初始模塊的快捷方式。初始模塊能夠通過指定deps配置屬性來加載。

[html] view plaincopy
  1. <script src="/path/to/dojo/dojo.js"><!--mce:3--></script>  

這是加載應用程序的一個非常棒的方式,因為JavaScript代碼能夠完全從HTML中消除,僅需留下一個腳本標記來引導整個剩餘的程序。同時,這種方式讓你能夠輕鬆的創建強勁的build,它能夠將你的應用程序代碼和dojo.js組合成為單獨的一個文件而不需要在build之後改變HTML腳本標簽。RequireJS和其他模塊加載器也有類似的加載頂層模塊的選項。


上圖展示了由require()調用引起的一連串的依賴加載。require()的調用開啟加載第一個模塊,接著根據需要加載各模塊的依賴模塊。那些不需要的模塊(如上圖中的模塊d)則永遠不會被加載或者執行。

require()函數還可用於配置模塊路徑查找以及其他選項,但這一般來說對各個模塊加載器都有特定的實現。更多信息請參考各個加載器關於配置細節的文檔。

插件和Dojo最優化

AMD還支持加載其它資源的插件。這一點對於加載非AMD依賴非常有價值,例如加載HTML片段和模板,CSS,國際化相關的特定資源等。插件機製讓我們在依賴列表中引用這些非AMD資源。語法如下:

[javascript] view plaincopy
  1. "plugin!resource-name"  

dojo/text插件就是一個常用的插件,它允許你直接將一個文件加載為一段文本。使用這個插件時,我們將目標文件列為資源名。在小部件加載它們的HTML模板時經常使用這個插件。例如,我們用下麵的方式可以通過Dojo創建我們自己的小部件:

[javascript] view plaincopy
  1. define(["dojo/_base/declare""dijit/_WidgetBase""dijit/_TemplatedMixin""dojo/text!./templates/foo.html"],  
  2.         function(declare, _WidgetBase, _TemplatedMixin, template){  
  3.     return declare([_WidgetBase, _TemplatedMixin], {  
  4.         templateString: template  
  5.     });  
  6. });  

這是一個多層麵創建Dojo小部件的範例。首先,它展示了利用Dijit基類創建小部件的標準用法。你可能也注意到我們如何創建一個小部件類並如何返回它。我們沒有使用任何命名空間或者類名來使用declare()(類構造方法)。因為AMD消除了對命名空間的需求,我們不再需要用declare()來創建全局的類名。這一點與AMD模塊中寫匿名模塊的策略是一致的。同樣的,一個匿名的模塊是不需要在其模塊內部硬編碼任何相對於自身的路徑或者名稱的。我們可以輕鬆的對模塊重命名或者將其移動到其他的路徑而不需要改模塊內的任何代碼。通常我們推薦使用這種方法來定義匿名類,但如果你需要用聲明式的標記來使用這個小部件的話,為了創建一個具有命名空間的全局變量,使其能夠讓Dojo 解析器在Dojo1.7中引用,你還是需要包含命名空間/類名來定義類。Dojo 1.8中對此作了改進,你可以使用模塊標識來做到這一點。

還有一些Dojo包含的插件是非常有用的。dojo/i18n插件在加載國際化區域性包(常用於翻譯文本或者區域信息格式化)時使用。另外一個重要的插件是dojo/domReady,它通常被推薦用於取代dojo.ready。如果一個模塊除了加載其他依賴還需要等待整個DOM可用才執行的時候,這個插件使得這一過程非常簡單,不需要再加一層額外的回調。我們將dojo/domReady作為插件使用時,但需要對應的資源名。

[javascript] view plaincopy
  1. define(["dojo/query""dojo/domReady!"],  
  2.         function(query){  
  3.    // DOM is ready, so we can query away  
  4.    query(".some-class").forEach(function(node){  
  5.        // do something with these nodes  
  6.    });  
  7. });  

另一個有價值的插件是dojo/has。 這個模塊用於輔助檢測某些特征,幫助你基於當前瀏覽器的某些特征來選擇不同的代碼路徑。然而這個模塊也常被用作一個標準模塊 ,提供一個has()函數,它也同時可以當作插件使用。作插件使用時能夠幫助我們根據當前的特性條件性的加載某些依賴。dojo/has插件的語法采用了一個三元操作符,它將特性名稱作為條件,而模塊標識作為值。例如,我們可以在當然瀏覽器支持touch事件的條件下加載單獨的touch UI模塊:

[javascript] view plaincopy
  1. define(["dojo/has!touch?ui/touch:ui/desktop"],  
  2.   function(ui){  
  3.     // ui will be ui/touch if touch is enabled,  
  4.     //and ui/desktop otherwise  
  5.     ui.start();  
  6. });  
這個三元操作符可以是嵌套的,空字符串可用於表示不加載任何模塊。

使用dojo/has的好處不僅僅是提供一個特征檢測的運行時API。如果使用dojo/has,不光在你的代碼中有has()形式,同時也作為依賴插件,build係統可以檢測這些特性的分支。這意味著我們可以創建設備或者瀏覽器相關的build,它們能夠為某些特定的特征集合進行高度優化,隻需要在build裏的staticHasFeatures選項定義期望的特性,然後build就會自動的處理相應的代碼分支。

數據模塊

對於沒有任何依賴的模塊,他們被簡單的定義為一個對象(就像數據一樣)。你可以使用僅有一個參數的define()調用,該參數就是那個對象。這是非常簡單和直接明了的。
[javascript] view plaincopy
  1. define({  
  2.     foo: "bar"  
  3. });  

這與JSONP非常類似,支持基於腳本的JSON數據傳輸。但是,實際上AMD對於JSONP的優勢在於它不需要請求任何URL參數,其目標可以是一個靜態文件,不需要服務器端任何支持代碼來為數據加上參數化的回調函數前綴。然而,這項技術必須小心使用。模塊加載器總是會對模塊進行緩存,因此後續的針對相同模塊標識的require()請求會產生同樣的緩存數據。這對你的檢索需求可能會造成一定的困擾。


構建(builds)

AMD 被設計得非常容易被構建工具解析從而創建出將多個模塊代碼連接在一起並壓縮的一個單獨文件。模塊化係統在這方麵提供了巨大的優勢因為構建工具能夠基於模塊中列出的依賴自動的生成這個構建文件,而不需要依賴任何手寫或者更新的腳本來創建。由於請求數目的減少,構建極大的減少了加載時間,並且由於依賴已經清楚的列在代碼中,因而使用AMD實現這一點簡直輕而易舉。

不使用構建


使用構建


(譯者注:原文作者選用的實驗截圖可能是本地資源加載的情況,由於本地資源加載的隨機性,在使用構建之後優勢不明顯。但實際在網絡傳輸中,使用構建會大大減少加載時間。)

性能

就像前麵提到的那樣,使用腳本元素注入比其他的方法快是因為它更依賴於原生的瀏覽器腳本加載機製。我們基於dojo.js創建了一些模塊的測試用例,腳本元素加載比使用XHR eval的方式快了大概60-90%。在Chrome中,如果有大量的小模塊,每個模塊加載的時間大概是5-6ms

,而XHR+eval方式平均每個模塊加載時間則接近9-10ms。在Firefox中,同步XHR方式比異步方式更快,而在IE中異步XHR比同步的快,但腳本元素加載無疑是最快的一個。讓我們感到意外的是IE9是最快的一個瀏覽器,不過這有可能是因為在Firefox和Chrome中debugger/inspector增加了一些額外的性能開銷。


模塊加載器

AMD API是開放的,現在已有有多個AMD模塊加載器和構造器的實現。這裏介紹幾個重要的AMD加載器:

  • Dojo – 這是一個完全的包括插件和構造器的AMD加載器。這是我們通常用來實現Dojo工具包的加載器。
  • RequireJS – 這是AMD加載器的元老也是AMD加載器的典範。其作者James Burke是AMD的主要作者和倡導者。這也是一個完整的包含構造器的加載器。
  • curl.js – 這是一個快速的AMD加載器,具有超級棒的插件支持(以及它自帶的插件庫)和自帶的構造器。
  • lsjs – 這是一個專門設計用於在本地存儲緩存模塊的AMD模塊加載器。其作者同時還寫了一個獨立的優化器。
  • NeedJS – 一個輕量級的AMD模塊加載器。
  • brequire – 另一個輕量級的AMD模塊加載器。
  • inject – 它是由LinkedIn創建並使用的,是一個快速輕量級的加載器,不提供對插件的支持。
  • Almond – 這是RequireJS的輕量級版本。

獲取AMD模塊

現在可以找到越來越多的AMD格式的包和模塊。Dojo 基礎包網站集中的給出了一個可以找到的包的列表。CPM 安裝器 可以用於安裝任何通過Dojo基礎包網站注冊的包(同時自動安裝他們的依賴)。


James Burke, RequireJS的作者創建了 Volo——一個支持直接從github上麵安裝包的安裝器。當然你也可以直接從他們的項目網站(在github或者其他地方)上直接下載模塊,然後自己組織你的目錄結構。
有了AMD,我們就能夠輕鬆的使用任何包而不僅僅是Dojo模塊來構造應用程序。將普通的腳本轉換成AMD也是非常簡單的。你隻需要在define中用一個空數組表麵依賴,然後將腳本直接包含在define的回調函數體中。當然如果該腳本必須在其他某些腳本之後執行,你也可以添加依賴。例如:
[javascript] view plaincopy
  1. my-script.js:  
  2. // add this to top of the script  
  3. defined([], function(){  
  4. // existing script  
  5. ...  
  6. // add this to the end of script  
  7. });  
我們可以構造用各種方式導入模塊的應用程序。
[javascript] view plaincopy
  1. require(["dgrid/Grid""dojo/query""my-script"], function(Grid, query){  
  2.     new Grid(config, query("#grid")[0]);  
  3. });  
必須提醒的一點是當將腳本轉換成模塊時,如果腳本含有頂層的函數或者變量,它們原本是全局函數或者變量,但是在define()回調函數之內它們變成了回調函數的局部函數和變量,因此不會產生全局的函數或者變量。你或者顯示的改變你的代碼來創建一個全局函數或者變量(刪掉那些變量前麵的var或者function前綴(當你知道該腳本要與其他依賴於這些全局變量的腳本共同工作時你很可能需要這麼做),或者改變轉換後的模塊使其返回函數或者變量作為模塊輸出並且讓其依賴模塊來使用這些輸出(這種方法使你能夠追求無全局變量的AMD典範)。

直接加載非AMD腳本

大多數模塊加載器同時支持直接加載非AMD腳本。我們可以將一個普通腳本包含在我們的依賴中,並用“.js”後綴或者提供一個URL絕對路徑或者用“\”開頭的URL來表示它們是非AMD的。加載的腳本不能夠提供直接的AMD輸出,但能夠通過標準的創建全局變量或者函數的形式來提供它自身的功能。例如我們可以加載Dojo和jQuery:
[javascript] view plaincopy
  1. require(["dojo""jquery.js"], function(dojo){ // jquery will be loaded as plain script  
  2.     dojo.query(...); // the dojo exports will be available in the "dojo" local variable  
  3.     $(...);   // the other script will need to create globals  
  4. });  

保持小的代碼體積

AMD能夠輕鬆的與多種腳本庫協同組合工作。然而,雖然能夠很容易實現這一點,但在某些方麵你必須小心。將Dojo和jQuery這樣的腳本庫組合也許能夠正常工作,但因為Dojo和jQuery在功能上絕大部分是重合的,它會導致增加很多不必要的下載量。事實上,Dojo新模塊策略的一個關鍵之處就在於避免下載任何多餘的模塊。除了向AMD轉換,Dojo基礎功能也被拆分成多個能夠單獨使用的模塊,使得Dojo能夠盡可能的為某個應用程序保持最小的體積。事實上,最新的Dojo 應用程序和組件開發(像dgrid)經常使得整個應用程序比原先版本的Dojo基類還要小。

AMD的反對意見

當然也有一些對AMD的反對聲音存在。一個反對意見是使用原來的CommonJS格式(AMD正是由此產生)比AMD更簡單明了,更不容易出錯。CommonJS格式的確沒有繁複的使用“禮節”。然而,這種格式也是有一些問題的。我們可以讓源文件原封不動的傳給瀏覽器。這需要模塊加載器將代碼封裝在一個注入了必須的CommonJS變量的頭部中,用這種方式來調用XHR和eval來加載模塊。這種方法的缺點我們之前已經討論過了,包括降低性能,難於在老版本的瀏覽器上調試,以及跨域訪問的限製。另一種方法是使用一個實時的構造過程,或者在服務器端按需封裝的機製對CommonJS模塊隻提供必要的封裝,這實際上跟AMD的思想是一致的。這些方法在很多情況下不一定會帶來太多麻煩,也是合理的開發方式。但是為了滿足最廣泛的用戶需求,這裏用戶可以在工作在一個非常簡單的服務器上,或者麵對跨瀏覽器的情況,或者使用老版本的瀏覽器,AMD減少了這些問題發生的幾率,這也是Dojo的使命之一。
AMD的依賴列舉機製也因為容易出錯而經常被詬病,因為它需要維護依賴列表和回調函數的參數列表時刻一致。如果這兩個列表不一致,模塊的引用就會大錯特錯。實際應用中,我們在這個問題上並沒有碰見太多的困難。另外還有一種使用AMD的替代方法可以解決這一問題。AMD支持調用僅有單個回調參數的define(),回調參數是一個require工廠方法,它包含了多個require()調用而不是一個依賴列表。這種方法不僅幫助解決如何保持依賴列表的同步問題,而且還使得添加CommonJs封裝變得輕而易舉。因為這個工廠函數的函數體是符合CommonJS模塊形式的。下麵就是一個使用這種方法來定義模塊的例子:
[javascript] view plaincopy
  1. define(function(require){  
  2.     var query = require("dojo/query");  
  3.     var on = require("dojo/on");  
  4.     ...  
  5. });  

當提供一個參數時,請求,輸出和模塊就會自動的提供給該工廠函數。AMD模塊加載器將會掃描該工廠函數的require調用,並自動的在運行該工廠方法之前加載他們。因為require調用直接內聯在變量賦值語句中,你可以很容易的刪除某個依賴聲明而不需要任何保持依賴同步的操作。
關於require() API的一個說明:當使用一個字符串來調用require(),它是同步執行的,但是如果把它放在隊列裏麵,它是一部執行的。因此例子中的依賴模塊仍然是在執行回調函數之前異步加載的,當依賴都加載到內存中,代碼中單字符串的require調用就被以同步的方式執行。

AMD的局限

AMD為我們提供了一個模塊加載並協同工作的重要層麵。然而,AMD僅僅是模塊定義。它並不能為模塊創建的API開出任何通用的“特別處方”。比如,你不能指望模塊加載器給你提供查詢引擎,並期望它從一堆可替換的查詢模塊中給你返回一個通用的API。當然定義這樣的API更利於模塊交互,但這不在AMD的範疇內。大多數模塊加載器不支持將模塊標識映射到不同的路徑,因此如果你有可替換的模塊,你最好自己定義一個模塊標識到不同目標路徑的映射來解決這一點。

漸進式加載

目前我們看到的AMD最大的問題不在於API,而是在實際應用中有種趨勢是預先聲明所有的依賴(這一點上文中我們一直這麼提,真抱歉我們現在才解釋這個問題!)。然而,很多模塊能夠正常的工作,而將某些依賴的加載延遲到它們實際需要的時候再加載。采用延遲加載策略是非常對提供一個漸進式加載頁麵是非常有價值的。有了這樣一個漸進式加載頁麵,頁麵上的組件能夠在其組件代碼下載完後就顯示,整個頁麵不需要等到所有JavaScript代碼都下載完就能渲染和使用。我們可以通過使用異步請求require([])API,將我們的模塊編寫成支持延遲加載特定模塊的形式。在下麵的例子中,我們隻為該函數加載必須的代碼來創建子容器節點,延遲加載容器內小部件的代碼,從而實現即時的視覺交互:
[javascript] view plaincopy
  1. // declare modules that we need up front  
  2. define(["dojo/dom-create""require"],  
  3.         function(domCreate, require){  
  4.     return function(node){  
  5.        // create container elements for our widget right away,  
  6.        // these could be styled for the right width and height,  
  7.        // and even contain a spinner to indicate the widgets are loading  
  8.        var slider = domCreate("div", {className:"slider"}, node);  
  9.        var progress = domCreate("div", {className:"progress"}, node);  
  10.        // now load the widgets, we load them independently  
  11.        // so each one can be rendered as it downloads  
  12.        require(["dijit/form/HorizontalSlider"], function(Slider){  
  13.           new Slider({}, slider);  
  14.        });  
  15.        require(["dijit/Progress"], function(Progress){  
  16.           new Progress({}, progress);  
  17.        });  
  18.     }  
這種方式提供了絕妙的用戶體驗,因為人們可以在組件一下載完成是就與之交互,而不需要等到整個頁麵全部加載完。如果用戶看到頁麵逐步渲染,很可能會感覺程序運行得比原來快,更富有響應。

require,exports

在上麵的例子中,我們使用了一個特殊的依賴——“請求(require)”,它是一個模塊內的require()函數的引用,允許我們使用相對於該模塊的引用。(如果你使用全局的“require”,相對模塊標識則不是相對於當前模塊的)
另外一個特殊的依賴是“輸出(exports)”。有了輸出依賴,輸出對象體現在參數中,而不是返回輸出對象。模塊可以向輸出對象添加屬性。這一點在模塊有循環引用的情況時特別有用。因為模塊工廠函數可以開始運行,並添加輸出對象,而另外一個函數可以在前者運行結束前就使用前者的輸出對象。關於循環引用使用輸出對象的一個簡單例子如下:
[javascript] view plaincopy
  1. main.js:  
  2. define(["component""exports"],  
  3.         function(component, exports){  
  4.     // we define our exported values on the exports  
  5.     // which may be used before this factory is called  
  6.     exports.config = {  
  7.        title: "test"  
  8.     };  
  9.     exports.start = function(){  
  10.         new component.Widget();  
  11.     };  
  12. });  
  13. component.js:  
  14. define(["main""exports""dojo/_base/declare"],  
  15.         function(main, exports, declare){  
  16.     // again, we define our exported values on the exports  
  17.     // which may be used before this factory is called  
  18.     exports.Widget = declare({  
  19.         showTitle: function(){  
  20.             alert(main.config.title);  
  21.         }  
  22.     });  
  23. });  

如果我們隻是依賴於函數的返回值,這個例子就不可能正常的工作,因為在循環中工廠函數需要先執行完畢,不可能訪問另一個函數的返回值。
就像再前麵的例子提到的那樣,如果我們省略依賴性列表,那麼依賴就被認做是缺省的”require“和”exports“,require()調用會被掃描,因此上例可以寫成:
[javascript] view plaincopy
  1. define(function(require, exports){  
  2.     var query = require("dojo/query");  
  3.     exports.myFunction = function(){  
  4.        ....  
  5.     };  
  6. });  


展望

EcmaScript 委員會致力於在JavaScript中增加原生模塊的支持。它們提供的添加方法是基於JavaScript語言中定義和引用模塊的新語法。這種新語法包括了一個module關鍵字用於在腳本中定義模塊,一個expoert關鍵字用於定義輸出,一個import關鍵字用以定義需要引入的模塊屬性。這些操作符與AMD中的概念一一對應,使得轉換變得相對容易。如果我們要將本文的第一個例子用Harmony 模塊係統定義的方法來寫,下麵是一種寫法:
[javascript] view plaincopy
  1. import {query} from "dojo/query.js";  
  2. import {on} from "dojo/on.js";  
  3. export function flashHeaderOnClick(button){  
  4.     on(button, "click"function(){  
  5.        query(".header").style("color""red");  
  6.     });  
  7. }  
現在提出的新模塊係統包括支持定製的模塊加載器,它能夠與新的模塊係統交互,還能夠用於保留某些AMD現存的某些特性,例如用插件訪問非JavaScript資源。

結論

AMD為基於瀏覽器的網絡應用提供了強大的模塊化係統,它利於原生的瀏覽器加載方式實現異步加載,支持對各種形式的資源靈活訪問的插件,同時用一個簡單明了的格式來實現它的功能。由於有了類似Dojo,RequireJS等眾多出色的AMD項目,AMD世界是讓人興奮的,並且為發展快速,可操作的JavaScript模塊帶來栩栩生機。.

最後更新:2017-04-03 05:40:07

  上一篇:go iOS7開發學習之路:No.10:XCode手動Clean資源文件,xib文件autolayout,對某個特定文件關閉ARC
  下一篇:go 判斷用戶是否開啟了定位功能