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
相关文章
相关标签/搜索