綜述
在Dojo1.7及以後的版本,模塊以
Asynchronous Module Definition (AMD)的格式書寫,取代了dojo.provide,dojo.require,dojo.requireIf,dojo.requireAfterIf,dojo.platformRequire和dojo.requireLocalization,包含徹底的異步操做,真正的包的可移植性,更好的依賴性管理和改進對調試的支持。這也是社區驅動的標準,這意味着寫入AMD規範的模塊可用於任何其它的AMD兼容的加載器或庫。
介紹AMD模塊標識符
新的AMD語法使得模塊標識符看起來像是路徑,而不是對象引用。這些新的標識符工做起來也很像路徑,在相同的包中可使用相似
./和../的相對片斷指向其它模塊。爲了加載任意的,非AMD代碼甚至可使用完整的URL做爲模塊標識符。
配置加載器
假定demo應用的文件系統結構以下:
- /
- index.html
- js/
- lib/
- dojo/
- dijit/
- dojox/
- my/
- util/
首先須要將
async設置爲true:
- <script data-dojo-config="async: true" src="js/lib/dojo/dojo.js"></script>
async也能夠在對象dojoConfig中設置,不管用哪一種方式,必須在加載器包含到頁面以前設置。如果省略了,加載器以向後兼容的遺留同步模式運行。
在異步模式中,加載器只定義了兩個全局函數:
require用於加載模塊,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表示假定未說起的非包,頂級模塊路徑是相對於baseUrl的。若是tlmSiblingOfDojo設置爲true,則這些假定爲dojo包的兄弟節點。這使得即便沒有明確的定義一個util包,也可使用util目錄中的代碼。最後一塊是該應用使用的定義的包的列表。
包
有三個主要的包配置選項。
name是包的名稱;location爲包的位置,能夠是相對於baseUrl的路徑或是一個絕對路徑;main爲可選的,默認爲main,當某人試圖請求包自己時,用於發現正確的模塊來加載。例如若試圖請求dojo,實際加載的文件是/js/dojo/main.js。由於已將my包的該屬性覆蓋了,因此某人請求my,實際加載的是/js/my/app.js。若要請求util,沒有對其定義,加載器將嘗試加載/js/util.js。
請求模塊
使用例子來解釋AMD風格的模塊加載:
- 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.8/" },
- { name: "my", location: "my" }
- ]
- }, [ "my/app" ]);
這裏略微改變了配置,將
dojo包指向Google CDN,說明AMD格式支持跨域加載。
當提供了配置對象,依然能夠傳遞依賴數組做爲第二個參數,而回調函數做爲第三個參數。
注意
async,tlmSiblingOfDojo,和已存在的has測試不能在運行時設置。此外,大多數配置數據是淺拷貝的,不能使用機制給定製的配置對象增長更多的鍵,該對象將會被重寫。
定義模塊
使用
define函數定義模塊。define調用和require調用相同,不一樣的只是回調函數返回一個被保存的值,用於模塊的resolved值。
- define([ "dojo/_base/declare", "dijit/_WidgetBase", "dijit/_TemplatedMixin" ], function(declare, _WidgetBase, _TemplatedMixin){
- return declare([ _WidgetBase, _TemplatedMixin ], {});
- });
注意這裏省略了可選的模塊簽名做爲第一個參數,例如
return declare("my._TemplatedWidget", [ _WidgetBase, _TemplatedMixin ], {});。
值得一提的是,定義模塊時回調函數僅調用一次,其返回值被加載器緩存。從實踐的角度看,這意味着經過依賴相同的模塊,模塊很容易共享對象。
相同的代碼在遺留模塊格式中:
- 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?"
- });
不使用回調函數將不能引用任何依賴,這一般僅用於i18n包中。javascript
使用可移植模塊
新的AMD加載器一個最重要的特徵是可以建立徹底可移植的包。新的加載器使得一個應用中使用來自兩個不一樣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配置屬性重映射整個路徑。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不一樣,只匹配完整的模塊標識符。Aliases也遞歸的匹配aliases,直到沒有新的匹配爲止。例如:
- var dojoConfig = {
- aliases: [
- [ "text", "dojo/text" ],
- [ "dojo/text", "my/text" ],
- [ "i18n", "dojo/i18n" ],
- [ /.*\/env$/, "my/env" ]
- ]
- };
- text => dojo/text
- dojo/text => my/text
- i18n => dojo/i18n
- foo => foo
- [anything]/env => my/env
使用
aliases,目標別名必須是徹底的模塊標識符,源別名必須是徹底的模塊標識符或正則表達式。
寫可移植模塊
爲了實現可移植性,任何內部包模塊引用要使用相對模塊標識符,例如:
- define([ "my/otherModule", "my/foo/bar" ], function(otherModule, bar){
- });
取代顯式的從
my包請求模塊,改用相對標識符:
- 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);
- });
- });
- });
可是當改用相對標識符,調用
require時,原始模塊的上下文丟失了。在原始的define調用中傳入特殊的上下文敏感的模塊標識符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調用本地綁定,上下文敏感的require函數,能夠安全的請求相對於my/debug的模塊。
使用插件
插件可用於爲加載器擴展新的特徵,而不僅是簡單的加載一個AMD模塊。插件的加載方式或多或少和常規的模塊相同,可是在模塊標識符的最後加入了一個特殊的!,做爲插件請求的標誌。在!以後的數據直接傳遞給插件來處理。Dojo默認含有插件,其中最重要是
dojo/text,dojo/i18n,dojo/has和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.require("dijit.Dialog");
- dojo.declare("my.Dialog", dijit.Dialog, {
- templateString: dojo.cache("my", "templates/Dialog.html")
- });
dojo/i18nhtml
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.require("dijit.Dialog");
- dojo.requireLocalization("my", "common");
- dojo.declare("my.Dialog", dijit.Dialog, {
- title: dojo.i18n.getLocalization("my", "common").dialogTitle
- });
dojo/hasjava
Dojo新的加載器包括
has.js特徵檢測API的實現,
dojo/has插件爲有條件的請求模塊調節了該功能,用法以下:
- 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!");
- });
- });
"my.events.w3c");
- dojo.requireIf(!window.addEventListener, "my.events.ie");
- my.events.addEvent(dom.byId("foo"), "click", function(evt){
- console.log("Foo clicked!");
- });
dojo/domReadynode
dojo/domReady是dojo.ready的替代,該模塊直到DOM加載完成才解析,用法以下:
- define(["dojo/dom", "dojo/domReady!"], function(dom){
- dom.byId("someElement");
- });
- 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是循環依賴的部分。爲了打破循環依賴,模塊A將自動解析爲空的對象,該空對象將做爲A的值傳遞給模塊B,而後模塊A的回調函數被調用,其返回值被丟棄。在上面的例子中,這意味着A將是空的對象,而不是有着stuff函數的對象,所以代碼不會按預期工做。git
爲了解決這個問題,加載器提供了特殊的
exports模塊標識符。這樣的話,該模塊將返回空的對象,用於解決循環依賴。當回調函數被調用,能夠附加屬性到exports。這時,stuff函數依然能夠成功定義並在後面使用:
- 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,如果其先被請求,將會因做爲循環依賴解決機制的目標模塊而結束,這種狀況下其會因被定義爲空的對象而結束。此外,如果模塊A須要返回一個函數而非對象,使用
exports將不起做用。由於這些緣由,只要可能的話,應用應當被重構,移除循環依賴。
加載非AMD代碼
AMD加載器也能夠經過傳遞一個標識符用於加載非AMD代碼,那實際上就是一個指向JavaScript文件的路徑。加載器以如下三種方式之一識別這些特殊的標識符:
- 標識符以 「/" 開頭
- 標識符以一個協議開頭 (例如 「http:」, 「https:」)
- 標識符以 「.js」 結束
當任意代碼被看成模塊加載,模塊的解析值爲
undefined,將須要直接訪問由腳本全局定義的任何代碼。
Dojo加載器的一個獨有的特徵是以AMD風格的模塊混合和匹配遺留的Dojo模塊的能力。這使得緩慢而有條理的從遺留的代碼庫過渡到AMD代碼庫成爲可能,而不須要當即轉變全部東西,不管加載器工做在同步模式仍是異步模式。
服務端JavaScript
新的AMD加載器的一個特徵在於可以使用
node.js和
Rhino加載服務器上的JavaScript。經過命令行加載Dojo:
- # 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>