Dojo學習筆記——定義模塊

綜述
在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應用的文件系統結構以下:
[plain] view plain copy
  1. /  
  2. index.html  
  3. js/  
  4. lib/  
  5. dojo/  
  6. dijit/  
  7. dojox/  
  8. my/  
  9. util/  
首先須要將 async設置爲true:
[html] view plain copy
  1. <script data-dojo-config="async: true" src="js/lib/dojo/dojo.js"></script>  
async也能夠在對象dojoConfig中設置,不管用哪一種方式,必須在加載器包含到頁面以前設置。如果省略了,加載器以向後兼容的遺留同步模式運行。
在異步模式中,加載器只定義了兩個全局函數: require用於加載模塊,define用於定義模塊。
接下來須要配置加載器,其中包含模塊位置的信息:
[javascript] view plain copy
  1. var dojoConfig = {  
  2. baseUrl: "/js/",  
  3. tlmSiblingOfDojo: false,  
  4. packages: [  
  5. { name: "dojo", location: "lib/dojo" },  
  6. { name: "dijit", location: "lib/dijit" },  
  7. { name: "dojox", location: "lib/dojox" },  
  8. { name: "my", location: "my", main: "app" }  
  9. ]  
  10. };  
在這個配置中, 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風格的模塊加載:
[javascript] view plain copy
  1. require([ "dojo/_base/declare", "dijit/_WidgetBase", "dijit/_TemplatedMixin" ], function(declare, _WidgetBase, _TemplatedMixin){  
  2. // "declare" holds the dojo declare function  
  3. // "_WidgetBase" holds the dijit _WidgetBase constructor  
  4. // "_TemplatedMixin" holds the dijit _TemplatedMixin constructor  
  5. // Do whatever you want with these modules here.  
  6. });  
require函數接受一個模塊標識符(依賴)數組做爲第一個參數,一個回調函數做爲第二個參數。其解決了按順序列出的每一個依賴項。一旦全部的依賴項得以解決,它們將做爲參數傳遞給回調函數。回調函數是可選的,若只是加載一些模塊而不對它們作任何事,能夠簡單忽略它。如果忽略了模塊標識符數組則意味着一個不一樣的操做模式,因此務必保持有一個,即便它是空的。
require函數也能夠用於在運行時從新配置加載器,經過傳遞一個配置對象做爲第一個參數:
[javascript] view plain copy
  1. require({  
  2. baseUrl: "/js/",  
  3. packages: [  
  4. { name: "dojo", location: "//ajax.googleapis.com/ajax/libs/dojo/1.8/" },  
  5. { name: "my", location: "my" }  
  6. ]  
  7. }, [ "my/app" ]);  
這裏略微改變了配置,將 dojo包指向Google CDN,說明AMD格式支持跨域加載。
當提供了配置對象,依然能夠傳遞依賴數組做爲第二個參數,而回調函數做爲第三個參數。
注意 async,tlmSiblingOfDojo,和已存在的has測試不能在運行時設置。此外,大多數配置數據是淺拷貝的,不能使用機制給定製的配置對象增長更多的鍵,該對象將會被重寫。

定義模塊
使用 define函數定義模塊。define調用和require調用相同,不一樣的只是回調函數返回一個被保存的值,用於模塊的resolved值。
[javascript] view plain copy
  1. // in "my/_TemplatedWidget.js"  
  2. define([ "dojo/_base/declare", "dijit/_WidgetBase", "dijit/_TemplatedMixin" ], function(declare, _WidgetBase, _TemplatedMixin){  
  3. return declare([ _WidgetBase, _TemplatedMixin ], {});  
  4. });  
注意這裏省略了可選的模塊簽名做爲第一個參數,例如 return declare("my._TemplatedWidget", [ _WidgetBase, _TemplatedMixin ], {});。
值得一提的是,定義模塊時回調函數僅調用一次,其返回值被加載器緩存。從實踐的角度看,這意味着經過依賴相同的模塊,模塊很容易共享對象。
相同的代碼在遺留模塊格式中:
[javascript] view plain copy
  1. dojo.provide("my._TemplatedWidget");  
  2. dojo.require("dijit._WidgetBase");  
  3. dojo.require("dijit._TemplatedMixin");  
  4. dojo.declare("my._TemplatedWidget", [ dijit._WidgetBase, dijit._TemplatedMixin ], {});  
  
  • define({  
  • greeting: "Hello!",  
  • howAreYou: "How are you?"  
  • });  

不使用回調函數將不能引用任何依賴,這一般僅用於i18n包中。javascript


使用可移植模塊
新的AMD加載器一個最重要的特徵是可以建立徹底可移植的包。新的加載器使得一個應用中使用來自兩個不一樣Dojo版本的模塊很容易實現。經過在包配置中加入 packageMap對象,使得在該包中隱式的重映射引用到其餘包成爲可能。加載兩個不一樣Dojo版本的包配置示例以下:
[javascript] view plain copy
  1. var map16 = { dojo: "dojo16", dijit: "dijit16", dojox: "dojox16" },  
  2. dojoConfig = {  
  3. packages: [  
  4. { name: "dojo16", location: "lib/dojo16", packageMap: map16 },  
  5. { name: "dijit16", location: "lib/dijit16", packageMap: map16 },  
  6. { name: "dojox16", location: "lib/dojox16", packageMap: map16 },  
  7. { name: "my16", location: "my16", packageMap: map16 },  
  8. { name: "dojo", location: "lib/dojo" },  
  9. { name: "dijit", location: "lib/dijit" },  
  10. { name: "dojox", location: "lib/dojox" },  
  11. { name: "my", location: "my" }  
  12. ]  
  13. };  
在該配置中,任什麼時候候其中一個包使用 map16包映射引用dojo,dijit或dojox,將隱式的重定向到dojo16,dijit16和dojox16。而全部其餘的代碼繼續使用正常的包。
也可使用 paths配置屬性重映射整個路徑。paths從字符串的開頭匹配模塊標識符的任何部分,以最長的匹配路徑爲準。例如:
[javascript] view plain copy
  1. var dojoConfig = {  
  2. paths: {  
  3. "my/debugger/engine": "my/debugger/realEngine",  
  4. "my/debugger": "other/debugger"  
  5. }  
  6. };  
  • 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,直到沒有新的匹配爲止。例如:
[javascript] view plain copy
  1. var dojoConfig = {  
  2. aliases: [  
  3. "text", "dojo/text" ],  
  4. "dojo/text", "my/text" ],  
  5. "i18n", "dojo/i18n" ],  
  6. [ /.*\/env$/, "my/env" ]  
  7. ]  
  8. };  
  • text => dojo/text
  • dojo/text => my/text
  • i18n => dojo/i18n
  • foo => foo
  • [anything]/env => my/env
使用 aliases,目標別名必須是徹底的模塊標識符,源別名必須是徹底的模塊標識符或正則表達式。

寫可移植模塊
爲了實現可移植性,任何內部包模塊引用要使用相對模塊標識符,例如:
[javascript] view plain copy
  1. // in "my/foo/blah.js"  
  2. define([ "my/otherModule", "my/foo/bar" ], function(otherModule, bar){  
  3. // …  
  4. });  
取代顯式的從 my包請求模塊,改用相對標識符:
[javascript] view plain copy
  1. // in "my/foo/blah.js"  
  2. define([ "../otherModule", "./bar" ], function(otherModule, bar){  
  3. // …  
  4. });  
請求一個模塊,例如,要延遲加載一個可選的模塊直到事件發生。若使用明確的模塊定義,這至關的簡單:
[javascript] view plain copy
  1. // in "my/debug.js"  
  2. define([ "dojo/dom", "dojo/dom-construct", "dojo/on" ], function(dom, domConstruct, on){  
  3. on(dom.byId("debugButton"), "click", function(evt){  
  4. require([ "my/debug/console" ], function(console){  
  5. domConstruct.place(console, document.body);  
  6. });  
  7. });  
  8. });  
可是當改用相對標識符,調用 require時,原始模塊的上下文丟失了。在原始的define調用中傳入特殊的上下文敏感的模塊標識符require做爲一個依賴,能夠解決上下文環境丟失致使的相對標識符失效問題。
[javascript] view plain copy
  1. // in "my/debug.js"  
  2. define([ "dojo/dom", "dojo/dom-construct", "dojo/on", "require" ], function(dom, domConstruct, on, require){  
  3. on(dom.byId("debugButton"), "click", function(evt){  
  4. require([ "./debug/console" ], function(console){  
  5. domConstruct.place(console, document.body);  
  6. });  
  7. });  
  8. });  
如今內部的 require調用本地綁定,上下文敏感的require函數,能夠安全的請求相對於my/debug的模塊。

使用插件
插件可用於爲加載器擴展新的特徵,而不僅是簡單的加載一個AMD模塊。插件的加載方式或多或少和常規的模塊相同,可是在模塊標識符的最後加入了一個特殊的!,做爲插件請求的標誌。在!以後的數據直接傳遞給插件來處理。Dojo默認含有插件,其中最重要是 dojo/text,dojo/i18n,dojo/has和dojo/domReady。

dojo/text

dojo/text是dojo.cache的替代,用於須要從文件(如一個HTML模板)加載一個字符串的時候。例如爲模板化部件加載模板,可能定義以下模塊:
[javascript] view plain copy
  1. // in "my/Dialog.js"  
  2. define([ "dojo/_base/declare", "dijit/Dialog", "dojo/text!./templates/Dialog.html" ], function(declare, Dialog, template){  
  3. return declare(Dialog, {  
  4. templateString: template // template contains the content of the file "my/templates/Dialog.html"  
  5. });  
  6. });  
  • dojo.require("dijit.Dialog");  
  • dojo.declare("my.Dialog", dijit.Dialog, {  
  • templateString: dojo.cache("my", "templates/Dialog.html")  
  • });  


dojo/i18n
html

dojo/i18n爲dojo.requireLocalization和dojo.i18n.getLocalization的替代,用法以下:
[javascript] view plain copy
  1. // in "my/Dialog.js"  
  2. define([ "dojo/_base/declare", "dijit/Dialog", "dojo/i18n!./nls/common"], function(declare, Dialog, i18n){  
  3. return declare(Dialog, {  
  4. title: i18n.dialogTitle  
  5. });  
  6. });  
  • dojo.require("dijit.Dialog");  
  • dojo.requireLocalization("my", "common");  
  • dojo.declare("my.Dialog", dijit.Dialog, {  
  • title: dojo.i18n.getLocalization("my", "common").dialogTitle  
  • });  


dojo/has
java

Dojo新的加載器包括 has.js特徵檢測API的實現, dojo/has插件爲有條件的請求模塊調節了該功能,用法以下:
[javascript] view plain copy
  1. // in "my/events.js"  
  2. define([ "dojo/dom", "dojo/has!dom-addeventlistener?./events/w3c:./events/ie" ], function(dom, events){  
  3. // events is "my/events/w3c" if the "dom-addeventlistener" test was true, "my/events/ie" otherwise  
  4. events.addEvent(dom.byId("foo"), "click", function(evt){  
  5. console.log("Foo clicked!");  
  6. });  
  7. });  
"my.events.w3c");  
  • dojo.requireIf(!window.addEventListener, "my.events.ie");  
  • my.events.addEvent(dom.byId("foo"), "click", function(evt){  
  • console.log("Foo clicked!");  
  • });  


dojo/domReady
node

dojo/domReady是dojo.ready的替代,該模塊直到DOM加載完成才解析,用法以下:
[javascript] view plain copy
  1. // in "my/app.js"  
  2. define(["dojo/dom", "dojo/domReady!"], function(dom){  
  3. // This function does not execute until the DOM is ready  
  4. dom.byId("someElement");  
  5. });  
  • dojo.byId("someElement");  
  • });  

  

  • define([ "b" ], function(b){  
  • var a = {};  
  • a.stuff = function(){  
  • return b.useStuff ? "stuff" : "things";  
  • };  
  • return a;  
  • });  
  • // in "my/b.js"  
  • define([ "a" ], function(a){  
  • return {  
  • useStuff: true  
  • };  
  • });  
  • // in "my/circularDependency.js"  
  • require([ "a" ], function(a){  
  • a.stuff(); // "things", not "stuff"  
  • });  

這裏加載器試圖加載模塊A,而後是模塊B,而後又是模塊A,注意模塊A是循環依賴的部分。爲了打破循環依賴,模塊A將自動解析爲空的對象,該空對象將做爲A的值傳遞給模塊B,而後模塊A的回調函數被調用,其返回值被丟棄。在上面的例子中,這意味着A將是空的對象,而不是有着stuff函數的對象,所以代碼不會按預期工做。git

爲了解決這個問題,加載器提供了特殊的 exports模塊標識符。這樣的話,該模塊將返回空的對象,用於解決循環依賴。當回調函數被調用,能夠附加屬性到exports。這時,stuff函數依然能夠成功定義並在後面使用:
[javascript] view plain copy
  1. // in "my/a.js"  
  2. define([ "b", "exports" ], function(b, exports){  
  3. exports.stuff = function(){  
  4. return b.useStuff ? "stuff" : "things";  
  5. };  
  6. return exports;  
  7. });  
  8. // in "my/b.js"  
  9. define([ "a" ], function(a){  
  10. return {  
  11. useStuff: true  
  12. };  
  13. });  
  14. // in "my/circularDependency.js"  
  15. require([ "a" ], function(a){  
  16. a.stuff(); // "stuff"  
  17. });  
注意雖然成功解決了兩個模塊,但依然是至關不穩定的狀況。由於也沒有更新模塊B,如果其先被請求,將會因做爲循環依賴解決機制的目標模塊而結束,這種狀況下其會因被定義爲空的對象而結束。此外,如果模塊A須要返回一個函數而非對象,使用 exports將不起做用。由於這些緣由,只要可能的話,應用應當被重構,移除循環依賴。

加載非AMD代碼
AMD加載器也能夠經過傳遞一個標識符用於加載非AMD代碼,那實際上就是一個指向JavaScript文件的路徑。加載器以如下三種方式之一識別這些特殊的標識符:
  • 標識符以 「/" 開頭
  • 標識符以一個協議開頭 (例如 「http:」, 「https:」)
  • 標識符以 「.js」 結束
當任意代碼被看成模塊加載,模塊的解析值爲 undefined,將須要直接訪問由腳本全局定義的任何代碼。
Dojo加載器的一個獨有的特徵是以AMD風格的模塊混合和匹配遺留的Dojo模塊的能力。這使得緩慢而有條理的從遺留的代碼庫過渡到AMD代碼庫成爲可能,而不須要當即轉變全部東西,不管加載器工做在同步模式仍是異步模式。
服務端JavaScript
新的AMD加載器的一個特徵在於可以使用 node.jsRhino加載服務器上的JavaScript。經過命令行加載Dojo:
[plain] view plain copy
  1. # node.js:  
  2. node path/to/dojo.js load=my/serverConfig load=my/app  
  3.   
  4. # rhino:  
  5. java -jar rhino.jar path/to/dojo.js load=my/serverConfig load=my/app  
每一個 load= 參數添加模塊到依賴列表中,一旦加載器加載完成將自動解析。在瀏覽器中,等價的代碼以下:
[html] view plain copy
    1. <script data-dojo-config="async: true" src="path/to/dojo.js"></script>  
    2. <script>require(["my/serverConfig", "my/app"]);</script
相關文章
相關標籤/搜索