原文地址:http://dojotoolkit.org/documentation/tutorials/1.7/modules/javascript
dojo如今支持在異步模塊異步(AMD)定義中加入模塊寫入功能了,這使得代碼更容易編寫和調試。在這一節中,咱們學習關於這個模塊的使用,並探討如何使用它。html
概述
異步模塊定義(AMD)格式是一種新的模塊格式,只使用於1.7版本以上。它取代了dojo.provide, dojo.require, dojo.requireIf, dojo.requireAfterIf, dojo.platformRequire, and dojo.requireLocalization的這種寫法。它提供了在模塊的風格上許多的dojo改進,包括充分的異步操做,真正的包裝可移植性,更好的依賴性管理和改善調試支持。它也是一個社區驅動的標準,這意味着模塊寫入的這種風格可用於任何其餘適合AMD裝載的模塊。咱們將給你演示怎樣使用這個新的格式,以及強調一些對於舊風格新特性的優點。java
介紹異步模塊的定義
在討論加載模塊以前,咱們先簡單的介紹下模塊是如何定義的。node
若是你之前使用過dojo的1.6版本或更早的版本,你首先會發現最大的不一樣的地方是,新的模塊定義看起來更像是文件的路徑了。例如:之前的dojox.encoding.crypto.Blowfish 如今將寫成dojox/encoding/crypto/Blowfish. 新的定義使你用起來更像是要處理文件路徑。你可使用"./"和"../"來相對引入在同一包下的其它模塊。你甚至可使用url來代替模塊定義來加載模塊。web
在介紹完新特性後,如今咱們就開始咱們的教程了。ajax
配置加載器
咱們的dojo總體文件夾結構應該是和下面的同樣正則表達式
/
index.html
js/
lib/
dojo/
dijit/
dojox/
my/
util/
第一步要作的就是設置加載爲異步加載,也就是設置async屬性爲trueapi
- <script data-dojo-config="async: true" src="js/lib/dojo/dojo.js"></script>
async的設置也能夠是在dofoConfig對象裏設置,但無論怎麼作,這個設置必須是在加載dojo以前設置。若是這個被忽略了,他就會使用同步操做。跨域
在異步的模式下,加載器只定義了兩種函數。瀏覽器
request 用於加載模塊
define 用於定義模塊
這裏就是與傳統的加載器有一個鮮明的對比。之前是加載全部模塊。如今是並非那麼作了。
下一步就是來配置咱們要加載的模塊了
- var dojoConfig = {
- baseUrl: "/js/",
- tlmSiblingOfDojo: false,
- packages: [
- { name: "dojo", location: "lib/dojo" },
- { name: "dijit", location: "lib/dijit" },
- { name: "dojox", location: "lib/dojox" },
- { name: "my", location: "my", main: "app" }
- ]
- };
在這個配置中,baseUrl被設定爲本地目錄,目錄中包含了咱們全部的JavaScript代碼,tlmSiblingOfDojo被設定爲false,避免混淆不清的行爲習慣致使模塊包裝路徑異常。最後是定義包列表,這裏util沒有被加到包裏,雖然也訪問util但使用器時對於加載的文件請求解析會有不一樣。
Packages
通常來講,包只是模塊的集合。dojo,dijit,dojox都是包的實例。然而,不一樣的目錄中的模塊的簡單集合,包還包含了一些額外的功能,這些功能顯着加強了模塊的可移植性和易於使用。
包有三個主要的配置選項。
name,是包的名字。
location 是包的位置,能夠是相對路徑的baseURL或絕對路徑。
main 是一個可選參數,默認爲「main」,若是有人試圖要求加載模塊,它能夠用來發現加載正確的模塊文件。例如,若是你嘗試要求「dojo」,將裝載的是實際的文件"/js/dojo/main.js"。既然咱們已經爲「我」包重寫此屬性,若是有人須要「my」,他們將載入「"/js/my/app.js"的。若是咱們試圖要求加載「util」,這是沒有定義的包,裝載機會嘗試加載"/js/util.js".。
如今,咱們已經正確配置加載器,讓咱們學習如何使用它,先來看require
Requiring 模塊
最好的解釋就是例子
- require([ "dojo/_base/declare", "dijit/_WidgetBase", "dijit/_TemplatedMixin" ], function(declare, _WidgetBase, _TemplatedMixin){
-
-
-
-
- });
正如你能夠看到,require 功能須要兩個參數。第一個參數是「依賴關係」的一個集合,第二個參數是一個回調函數。經過列出的順序解決了模塊的依賴。一旦解決了全部的依賴,他們就會傳遞給回調函數。回調函數是可選的,因此若是你沒有作任何功能。只是要加載一些模塊,你能夠簡單地忽略它。若是忽略依賴關係則意爲着是另外一種操做方案,因此必定要保證有一個依賴在裏面,即便它是空的。
require 功能也可用於在運行時從新配置裝載機在配置對象時做爲第一個參數傳遞進去
- require({
- baseUrl: "/js/",
- packages: [
- { name: "dojo", location: "//ajax.googleapis.com/ajax/libs/dojo/1.7.1/" },
- { name: "my", location: "my" }
- ]
- }, [ "my/app" ]);
在這裏,咱們已經改變了配置,定義dojo包到一個Google的CDN地址。不一樣於傳統的加載模塊格式,在異步AMD的格式中是隱含支持跨域加載的。
請注意,並不是全部的配置選項是能夠在運行時設置。特別是async,tlmSiblingOfDojo,和has,一旦加載器加載後是不能改變的。此外,大多數的配置數據是淺複製,這意味着你不能使用這個機制,例如,添加自定義配置對象將會被覆蓋
Defining
模塊
定義模塊使用define 函數完成。define也調用require,但回調的返回值保存的新定義的模塊。
-
- define([ "dojo/_base/declare", "dijit/_WidgetBase", "dijit/_TemplatedMixin" ], function(declare, _WidgetBase, _TemplatedMixin){
- return declare([ _WidgetBase, _TemplatedMixin ], {});
- });
上面的例子,咱們經過dojo.declare建立和返回模塊。定義模塊時要注意的一件重要的事情是,回調函數只調用一次返回值並由裝載器緩存。在實踐層面,這意味着模塊能夠很容易地共享對象。
相對應的,使用之前的版本的代碼看起來像這樣:
- dojo.provide("my._TemplatedWidget");
- dojo.require("dijit._WidgetBase");
- dojo.require("dijit._TemplatedMixin");
- dojo.declare("my._TemplatedWidget", [ dijit._WidgetBase, dijit._TemplatedMixin ], {});
當定義一個模塊時,也能夠把值定義爲一個對象
-
- define({
- greeting: "Hello!",
- howAreYou: "How are you?"
- });
請記住,若是你定義一個模塊,而沒有使用一個回調函數,你將不可以引用任何依賴,所以這種類型的定義是不經常使用的,一般只是在取國際化時使用。
使用快捷式模塊
新的異步加載機最重要的特色之一是可以建立徹底快捷式模塊。舉例來講,若是你有一個應用程序,須要使用兩個不一樣版本的Dojo模塊,新的加載器能夠很容易的經過增長一個packageMap對象包的配置就有可能引用其餘包,該包內進行了從新映射。鑑於加載兩個不一樣的Dojo版本並排的例子,包配置看起來像這樣:
- var map16 = { dojo: "dojo16", dijit: "dijit16", dojox: "dojox16" },
- dojoConfig = {
- packages: [
- { name: "dojo16", location: "lib/dojo16", packageMap: map16 },
- { name: "dijit16", location: "lib/dijit16", packageMap: map16 },
- { name: "dojox16", location: "lib/dojox16", packageMap: map16 },
- { name: "my16", location: "my16", packageMap: map16 },
- { name: "dojo", location: "lib/dojo" },
- { name: "dijit", location: "lib/dijit" },
- { name: "dojox", location: "lib/dojox" },
- { name: "my", location: "my" }
- ]
- };
在上面的配置中,任什麼時候候使用map16來引用dojo,dijit,或dojox,他們將重定向到dojo16,dijit16,dojox16,而全部其餘的代碼將繼續正常使用的軟件包。
使用paths屬性也有可能從新映射整個路徑。路徑匹配是從任何一個模塊的標識符字符串的開頭開始的部分開始,選用最長匹配的路徑。例如:
- var dojoConfig = {
- paths: {
- "my/debugger/engine": "my/debugger/realEngine",
- "my/debugger": "other/debugger"
- }
- };
鑑於此路徑的配置,下面是路徑解析的替換:
my/debugger => other/debugger
my/debugger/foo => other/debugger/foo
my/debugger/engine/ie => my/debugger/realEngine/ie
not/my/debugger => not/my/debugger
最後,新的加載器還提供了一個aliases 配置屬性,不一樣於paths只匹配完整的模塊標識符。別名還遞歸匹配的別名,直到沒有新的匹配結果。例如
- var dojoConfig = {
- aliases: [
- [ "text", "dojo/text" ],
- [ "dojo/text", "my/text" ],
- [ "i18n", "dojo/i18n" ],
- [ /.*\/env$/, "my/env" ]
- ]
- };
對應的解析結果
text => my/text
dojo/text => my/text
i18n => dojo/i18n
foo => foo
[anything]/env => my/env
使用別名時,目標別名必須是絕對的模塊標識符,源別名必須是絕對的模塊標識符或正則表達式。
編寫可移植模塊
在裝載機爲了可以執行全部的可移植性,任何包內的模塊參考使用相對模塊標識符,這一點很重要。所以,舉例來講,下面的代碼
-
- define([ "my/otherModule", "my/foo/bar" ], function(otherModule, bar){
-
- });
改爲爲相對路徑的代碼
-
- define([ "../otherModule", "./bar" ], function(otherModule, bar){
-
- });
記住相對標識符只能用來指向在相同的包中的模塊
引入有條件的模塊
有時候,你可能想須要一個符合某些條件的模塊。例如,你可能要延遲加載一個可選模塊。若是你使用明確路徑的模塊定義這是很是簡單的:
-
- define([ "dojo/dom", "dojo/dom-construct", "dojo/on" ], function(dom, domConstruct, on){
- on(dom.byId("debugButton"), "click", function(evt){
- require([ "my/debug/console" ], function(console){
- domConstruct.place(console, document.body);
- });
- });
- });
不幸的是,想要徹底可移植,"my/debug/console"必須轉向到一個相對標識符。只是改變位置路徑值是不行的,由於原始模塊中已經require過了。爲了解決這個問題,dojo加載器提供了一種叫作上下文敏感的require。爲了使用其中之一,在初始化定義時,就要經過特殊的模塊標識符「require」做爲依賴:
-
- define([ "dojo/dom", "dojo/dom-construct", "dojo/on", "require" ], function(dom, domConstruct, on, require){
- on(dom.byId("debugButton"), "click", function(evt){
- require([ "./debug/console" ], function(console){
- domConstruct.place(console, document.body);
- });
- });
- });
如今咱們就可使用require去關聯到my/debug了
使用插件
異步加載器在除了加載原有模塊外還能夠加載一個新的模塊類型,稱爲插件。使用插件來擴展新的功能。做爲一個常常性的插件加載模塊是和原有模塊使用一樣的方式,可是在模塊標識符中使用!來標識這是以一個插件請求的require.將直接經過插件進行處理。這將變得更加清晰,咱們看幾個例子。 Dojo有一些默認插件;五個最重要的是dojo/text, dojo/i18n, dojo/has, dojo/load, 和 dojo/domReady。讓咱們來看看他們是如何使用。
dojo/text
dojo/text是更換dojo.cache,用來須要從一個文件(如HTML模板)加載一個字符串。例如,加載一個模板部件的模板,你會這樣定義你的模塊
-
- define([ "dojo/_base/declare", "dijit/Dialog", "dojo/text!./templates/Dialog.html" ], function(declare, Dialog, template){
- return declare(Dialog, {
- templateString: template
- });
- });
相對應於之前的版本
- dojo.provide("my.Dialog");
- dojo.require("dijit.Dialog");
- dojo.declare("my.Dialog", dijit.Dialog, {
- templateString: dojo.cache("my", "templates/Dialog.html")
- });
dojo/i18n
dojo/i18n替代了dojo.requireLocalization和dojo.i18n.getLocalization
-
- define([ "dojo/_base/declare", "dijit/Dialog", "dojo/i18n!./nls/common"], function(declare, Dialog, i18n){
- return declare(Dialog, {
- title: i18n.dialogTitle
- });
- });
相對應於之前的版本代碼
- dojo.provide("my.Dialog");
- dojo.require("dijit.Dialog");
- dojo.requireLocalization("my", "common");
- dojo.declare("my.Dialog", dijit.Dialog, {
- title: dojo.i18n.getLocalization("my", "common").dialogTitle
- });
dojo/has
新加載器實現加載了has.js功能
-
- define([ "dojo/dom", "dojo/has!dom-addeventlistener?./events/w3c:./events/ie" ], function(dom, events){
-
- events.addEvent(dom.byId("foo"), "click", function(evt){
- console.log("Foo clicked!");
- });
- });
相對應於之前的版本代碼
- dojo.requireIf(window.addEventListener, "my.events.w3c");
- dojo.requireIf(!window.addEventListener, "my.events.ie");
- my.events.addEvent(dom.byId("foo"), "click", function(evt){
- console.log("Foo clicked!");
- });
dojo/load
dojo/load和dojo/has很類似,但dojo/load使用了兩次模塊定義並返回模塊加載集合
-
- define([ "dojo/load!./sniffRenderer" ], function (renderer) {
-
- });
-
-
- define([], function () {
-
-
- return [ sniffModuleIdOfRenderer() ];
- });
但有多個模塊被定義時,只取第一個模塊。若是沒有模塊被定義,將標識undefined
相對應於之前的版本代碼
- var module = sniffModuleIdOfRenderer();
- dojo.require(module);
- var renderer = dojo.getObject(module);
-
dojo/domReady
dojo/domReady替換了dojo.ready,當只有dom初始化後纔會執行模塊
-
- define(["dojo/dom", "dojo/domReady!", function(dom){
-
- dom.byId("someElement");
- });
注意這裏沒有使用回調函數,由於這是沒有意義的,這只是進行了一個延遲的操做
相對應於之前的版本代碼
- dojo.ready(function(){
- dojo.byId("someElement");
- });
處理循環依賴
當你寫代碼時,你可能偶爾會遇到的狀況,你有兩個模塊,須要互相引用,此引用就造成了循環的依賴。爲了解決這樣一個循環依賴,加載器在第一次遞歸時就當即解決問題,。例如,考慮如下的例子
-
- define([ "b" ], function(b){
- var a = {};
- a.stuff = function(){
- return b.useStuff ? "stuff" : "things";
- };
-
- return a;
- });
-
-
- define([ "a" ], function(a){
- return {
- useStuff: true
- };
- });
-
-
- require([ "a" ], function(a){
- a.stuff();
- });
加載器在這種狀況下,將嘗試加載模塊A,模塊B,對於模塊A和會發現兩個模塊是一個循環依賴。爲了打破循環依賴,模塊將被自動解析爲一個空的對象。空對象將傳遞A的值到模塊B,而後模塊A的回調函數將被調用,其返回值被丟棄。在上面的例子中,這意味着將是一個空的對象,而不是一個功能的對象,因此咱們的代碼沒有如預期般運做。
爲了解決這個問題,加載器提供了一個特殊的「
exports 」模塊標識符。使用時,該模塊將返回空對象被用來解決循環依賴。回調被調用時,它能夠附加屬性出口。這種方式,功能仍然能夠成功地定義和使用:
-
- define([ "b", "exports" ], function(b, exports){
- exports.stuff = function(){
- return b.useStuff ? "stuff" : "things";
- };
-
- return exports;
- });
-
-
- define([ "a" ], function(a){
- return {
- useStuff: true
- };
- });
-
-
- require([ "a" ], function(a){
- a.stuff();
- });
請記住,雖然咱們已經成功地解決了這兩個模塊的循環依賴問題,但這還是一個至關不穩定的。由於咱們沒有更新模塊B,若是是先要加載B而不是A的話又會出現循環依賴的問題。在這種狀況下,它最終將被定義爲一個空的對象。此外,若是模塊A須要返回一個函數,而不是一個對象,使用"exports" ,將沒法正常工做。對於這些緣由,只要有可能,代碼應該重構,以消除循環依賴。
加載非異步的代碼
模塊標識符部分中提到,異步加載器還能夠經過一個標識符用來加載非異步的代碼,其實是一個JavaScript文件的路徑。裝載機在如下三種方式之一肯定這些特殊的標識符:
標識符開始「/」
標識符開始的協議(例如「HTTP:」,「HTTPS:」)
標識符結束「.js」
當任意代碼做爲一個模塊加載,模塊的值是undefined的,您將須要直接訪問代碼腳本的全局定義。
最後一個功能是dojo加載器可以混合和匹配與異步式模塊遺留下來的Dojo模塊。這使得它能夠從一個傳統的codebase AMD的代碼庫,慢慢地,有條不紊地過渡。而不須要當即轉換一切。當在異步模式下,解決遺留模塊的值對象,是在全局範圍內使用dojo.provide調用。例如:
-
- dojo.provide("my.legacyModule");
- my.legacyModule = {
- isLegacy: true
- };
當異步加載器加載到require(["my/legacyModule"]),處理的方式是把對象賦值於my.logacyModule
服務端腳本
最後的一個新功能是新異步加載器所擁有的使用node.js或Rhino加載服務器腳本。
- # node.js:
- node path/to/dojo.js load=my/serverConfig load=my/app
-
- # rhino:
- java -jar rhino.jar path/to/dojo.js load=my/serverConfig load=my/app
每一個load=屬性都將自動加入模塊依賴。在瀏覽器中,等價的代碼以下
- <script data-dojo-config="async: true" src="path/to/dojo.js"></script>
- <script>require(["my/serverConfig", "my/app"]);</script>
結論
dojo的異步加載器提供了更多的功能和特性,雖然代碼相對有點長。這裏只是簡短的介紹,想要了解更多內容請查看參考文檔。