综述
在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>