在学习Dojo的时候,第一个问题就是加载器的原理,但在看dojo.js这个加载器的源码的时候,因为没有基础,只知道AMD的规范require 及 define. 所以没办法理清楚整个加载器的代码结构。所有先学习如何来使用 AMD规范才是首要任务,在去学习dojo loader原理,在去学习源代码实现,所以有了这遍文章。
Dojo 已经支持模块用AMD规范来写, 通过这种方式,模块更注意书写及调试,本教程中, 我们将学习所有关于这个新的模块格式,及如何在自己的应用里面使用它。
* 本教程是在 AMD 加载器基础教程之后,所以首先请确定你已经了解了基础 的AMD规范。
贯穿整个教程,我们都将参考一个假设的应用程序, 它的文件结构如下
/ index.html js/ lib/ dojo/ dijit/ dojox/ my/ util/
require({ baseUrl: "/js/", packages: [ { name: "dojo", location: "//ajax.googleapis.com/ajax/libs/dojo/1.9.2/" }, { name: "my", location: "my" } ] }, [ "my/app" ]);
// in "my/nls/common.js" define({ greeting: "Hello!", howAreYou: "How are you?" });
// my/app.js defined("5") // index.html require(["my/app"],function(app){ console.log(app); //output 5 })
<script data-dojo-config="async: true" src="js/lib/dojo/dojo.js"></script>除非你只想使用同步模式,否则你应该养成上面的输写习惯。下一步我们需要配置一些让loader知道我们模块在哪里的信息。
var dojoConfig = { baseUrl: "/js/", //服务器绝对路径,如果需要相对于index.html, 请指定为./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" } ] };
/js/my/widget/Person.js它允许我们在文件系统里方便的存储,只需设定一个根目录,其它的部分只要相对于根baseUrl就行了。 而不需要每次都是 require(['js/my/widget/Person'], 我们可以简单如 require(["my/widget/Person"])。
/ js/ dojo/ dijit/ dojox/ my/ util/
dojoConfig = { packages: [ { name: "dojo16", location: "lib/dojo16" }, { name: "dijit16", location: "lib/dijit16" }, { name: "dojox16", location: "lib/dojox16" }, { name: "dojo", location: "lib/dojo" }, { name: "dijit", location: "lib/dijit" }, { name: "dojox", location: "lib/dojox" }, { name: "myOldApp", location: "myOldApp" }, { name: "my", location: "my" } ], map: { myOldApp: { dojo: "dojo16", dijit: "dijit16", dojox: "dojox16" } } };
你可以参考AMD 配置文档来了解更多map信息
* 如果你已经对loader比较熟悉,特别是packageMap 已经被弃用。 map配置方法是新的使用方法。
// in "my/widget/NavBar.js" define([ "dojo/dom", "my/otherModule", "my/widget/InfoBox" ], function(dom, otherModule, InfoBox){ // … });
// in "my/widget/NavBar.js" define([ "dojo/dom", "../otherModule", "./InfoBox" ], function(dom, otherModule, InfoBox){ // … });
考虑一下同一个包中相对标识符的限制。 首先让我们回顾一下map的例子, 你有发现哪有错误吗? 简单的讲,我们只关注了自己写的应用程序使用新老两个版本(myOldApp 使用dojo16, app使用dojo)。 然而我们忽视了一个重要的事情,库的依赖 - dijit 依赖 Dojo, DojoX依赖Dojo及Dijit. 以下的配置将会确保这些依赖可以得到解析。 为了安全起见, 我们也将包映射到了他们自己(map:{ dojo16: {dojo:"dojo16"}}) ,在这种情况下的任何模块都不能使用相对标识符(测试了下,好像可以使用相对标识,不知道说的是什么样的一个情况)。
var map16 = { dojo: "dojo16", dijit: "dijit16", dojox: "dojox16" }; dojoConfig = { packages: [ { name: "dojo16", location: "lib/dojo16" }, { name: "dijit16", location: "lib/dijit16" }, { name: "dojox16", location: "lib/dojox16" }, { name: "dojo", location: "lib/dojo" }, { name: "dijit", location: "lib/dijit" }, { name: "dojox", location: "lib/dojox" }, { name: "myOldApp", location: "myOldApp" }, { name: "my", location: "my" } ], map: { dojo16: map16, dijit16: map16, dojox16: map16, myOldApp: map16 } };
// in "my/debug.js" define([ "dojo/dom", "dojo/dom-construct", "dojo/on" ], function(dom, domConstruct, on){ on(dom.byId("debugButton"), "click", function(){ require([ "my/debug/console" ], function(console){ domConstruct.place(console, document.body); }); }); });
上面代码不好的就是可移性不好,所以需要将”my/debug/console ” 转化为相对路径。 仅仅是将"my/debug/console"变为"./debug/console"又不能使require工作, 因为当require被调用的时候原来模块的上下文已经丢失。 为了解决这个问题, Dojo loader 提供了一个上下文加载器 (context-sensitive require). 使用时,需要在define 的依赖列表中指定一个特别的模块标识符"require".
// in "my/debug.js" define([ "dojo/dom", "dojo/dom-construct", "dojo/on", "require" ], function(dom, domConstruct, on, require){ on(dom.byId("debugButton"), "click", function(){ require([ "./debug/console" ], function(console){ domConstruct.place(console, document.body); }); }); });
* 上下文为什么会丢失?
记住, require是一个全局定义的函数。 当你执行"click"时, 唯一的上下文是定义"click"事件模块的作用域,它不知道模块是什么. 在局部作用域内没有require函数, 所有全局的require函数会被调用。 回忆下我们之前说的文件结构, 如果我们传递"./debug/console" 给到全局的require, 它将试着去加载" js/debug/console.js", 但不存在这个文件。
当你使用content-sensitive require(上下文require), 我们可以局部的引用一个修改的require函数,它可以维持模块的上下文环境, 所以它能正确的加载"/js/my/debug/console.js".
Content-sensitive require 也可以为一个模块来用来加载资源(图片,模板, css)。 如以下的文件结构
/ js/ my/ widget/ InfoBox.js images/ info.png
在 InfoBox.js 内,我们可以通过调用require.toUrl来获得"info.png"的完整路径,然后将图片的src设置为这个路径。
// in "my/moduleA.js" define([ "./moduleB" ], function(moduleB){ return { getValue: function(){ return "oranges"; }, print: function(){ // dependency on moduleB log(moduleB.getValue()); } }; }); // in "my/moduleB.js" define([ "./moduleA" ], function(moduleA){ return { getValue: function(){ // dependency on moduleA return "apples and " + moduleA.getValue(); } }; }); // in "index.html" require([ "my/moduleA" ], function(moduleA) { moduleA.print(); });
// in "my/moduleA.js" define([ "./moduleB", "exports" ], function(moduleB, exports){ exports.getValue = function(){ return "oranges"; }; exports.print = function(){ log(moduleB.getValue()); }; }); // in "my/moduleB.js" define([ "./moduleA" ], function(moduleA){ return { getValue: function(){ return "apples and " + moduleA.getValue(); } }; }); // in "index.html" require([ "my/moduleA" ], function(moduleA) { moduleA.print(); });
// in "my/moduleA.js" define([ "./moduleB", "exports" ], function(moduleB, exports){ exports.isValid = true; exports.getValue = function(){ return "oranges"; }; exports.print = function(){ // dependency on moduleB log(moduleB.getValue()); } }); // in "my/moduleB.js" define([ "./moduleA" ], function(moduleA){ // this code will run at resolution time, when the reference to 这段代码将运行在模块解析时, 这时引用的模块A为一个空对象,所以moduleA.isValid 是一个undefined. // moduleA is an empty object, so moduleA.isValid will be undefined if(moduleA.isValid){ return { getValue: function(){ return "won't happen"; } }; } // this code returns an object with a method that references moduleA 这段代码将会返回一个引用了moduleA的方法的对象,getValue不能在moduleA没有被实际解析之前调用。 由于模块A中使用了exports. 所以可以调用moduleA的getValue()方法. // the "getValue" method won't be called until after moduleA has // actually been resolved, and since it uses exports, the "getValue" // method will be available return { getValue: function(){ return "apples and " + moduleA.getValue(); } }; }); // in "index.html" require([ "my/moduleA" ], function(moduleA) { moduleA.print(); });
// in "my/legacyModule.js" dojo.provide("my.legacyModule"); my.legacyModule = { isLegacy: true };
# 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
<script data-dojo-config="async: true" src="path/to/dojo.js"></script> <script>require(["my/serverConfig", "my/app"]);</script>