综述
在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应用的文件系统结构如下:
  1. /  
  2. index.html  
  3. js/  
  4. lib/  
  5. dojo/  
  6. dijit/  
  7. dojox/  
  8. my/  
  9. util/  
首先需要将async设置为true:
  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包中。


使用可移植模块
新的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

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

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

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函数的对象,因此代码不会按预期工作。

为了解决这个问题,加载器提供了特殊的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:
  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= 参数添加模块到依赖列表中,一旦加载器加载完成将自动解析。在浏览器中,等价的代码如下:
    1. <script data-dojo-config="async: true" src="path/to/dojo.js"></script>  
    2. <script>require(["my/serverConfig", "my/app"]);</script
posted on 2016-12-30 13:07  Sharpest  阅读(253)  评论(0编辑  收藏  举报