1、概述
AMD(异步模块加载定义)规范是Dojo1.7版本开始采用的一种模块规范,相较于Dojo以前采用的模块风格,它在许多方面做了很大的改进和优化,比如完整的异步操作,真正的包可移植性,更好的依赖管理以及改进的debug支持。AMD是一个社区驱动的标准,这意味着,采用AMD规范编写的模块可以很容易地被其他AMD规范兼容的loader加载器或者库使用。
2、什么是模块
模块是一个可以通过单独的引用而被访问的值。如果你希望将一个模块中的很多数据和函数暴露给外界,那么它们就必须成为用一个对象来代表这个模块,然后将那些希望能被访问的内容作为这个对象的属性。从文件系统的角度看,一个模块被保存为一个文件。
3、什么是加载器
加载器(loader)也是JavaScript代码,用于实现定义和加载模块的函数逻辑。当你载入dojo.js 或者require.js的时候,你就已经获得了一个AMD的加载器,这个加载器定义了与之进行交互的两个全局函数 “require” 和“define”。其中require是加载和使用模块,define是用来定义自己的模块。
4、如何定义一个模块
在AMD中,我们通过给加载器注册模块的方式来定义它。
示例一:模块的值就是一个数字
define(5);
示例二:当这个模块被加载后,我们可以得到一个拥有两个属性的对象
define({ library: 'dojo', version: 1.9 });
示例三:将一个函数传递给define
define(function(){ var privateValue = 0; return { increment: function(){ privateValue++; }, decrement: function(){ privateValue--; }, getValue: function(){ return privateValue; } }; });
在这个例子中,我们将一个函数传递给了define,加载器会调用这个函数并且将他的结果保存为一个模块,这段代码采用闭包生成了一个不能被外部代码直接访问的私有数据,但是能通过一个对象的方法来进行检查和操作,这个对象最后作为模块的值而返回。
5、如何加载一个模块
为了加载模块,你需要某些方式来标识它是一个可以被加载的模块。类似于其他编程语言的模块/包 系统,一个AMD模块是通过文件路径和文件名来标识的。
(1)网站根目录下,创建文件app/counter.js,代码如下
define(function(){ var privateValue = 0; return { increment: function(){ privateValue++; }, decrement: function(){ privateValue--; }, getValue: function(){ return privateValue; } }; });
(2)网站根目录下,创建html文件index.html
<html> <head> <meta charset="UTF-8"> <title>Dojo AMD</title> </head> <body> <script src="dojo/dojo.js" data-dojo-config="async: true"></script> <script> require([ "app/counter" ], function(counter){ console.log(counter.getValue()); counter.increment(); console.log(counter.getValue()); counter.decrement(); console.log(counter.getValue()); }); </script> </body> </html>
输出结果:
0
1
0
说明:
(1)在app/counter.js,我们调用define函数将模块注册到加载器中。需要注意的是本例中,我们是通过一个对象而不是构造函数引用这个模块,这意味着,任何加载这个模块的代码都会获得一个完全一致的对象。通常来说,模块返回的都是构造函数,但是在某些情况下,会返回一个单例的对象。
(2)通过在文件系统中的index.html所在文件夹中的子文件夹以及AMD加载器(dojo/dojo.js)的兄弟文件夹定位我们的模块,我们并不需要额外的配置来让加载器知道模块id,“app/counter”就表示要加载app/counter.js这个文件并且将它的返回值作为模块。
(3)在我们的index.html中,我们调用require函数来加载”app/coounter”模块,我们可以简单地用require(["app/counter"])来加载模块。
6、模块加载其他模块
当我们的程序是由许多组织良好的模块组成的时候,这些模块之间必然存在许多的依赖关系。Define 函数可以自动为你的模块加载依赖模块。依赖关系列表参数是先于模块的值传递给define函数的。
define([ "dojo/_base/declare", "dojo/dom", "app/dateFormatter" ], function(declare, dom, dateFormatter){ return declare(null, { showDate: function(id, date){ dom.byId(id).innerHTML = dateFormatter.format(date); } }); });
多个依赖模块-dojo/dom模块和假设的“app/dataFormatter”模块被声明在依赖关系列表参数中。
返回一个构造函数,这个模块的名字定义为 app/DateManger。调用这个模块的代码示例如下:
require([ "app/DateManager" ], function(DateManager){ var dm = new DateManager(); dm.showDate('dateElementId', new Date()); });
7、使用插件
除了通常的模块之外,AMD加载器的另一个功能点就是一种新的被称为插件的模块。插件是用于扩展加载器的功能,使得它不再仅仅用于加载AMD模块,插件差不多像其他普通模块一样以同样的方式被加载,但是在模块标识符的后面会添加一个特殊的字符“!”来表明这是一个插件请求。“!”后面的数据会直接传递给插件进行处理。四个最重要的插件:dojo/text, dojo/i18n, dojo/has 和 dojo/domReady。
(1)dojo/text
dojo/text 是用于从文件(比如HTML 模板)中加载字符串, 这些字符串会被缓存起来,这样,后面再次调用加载同样的文件的时候 就不会再需要额外的网络请求了。构建器使用dojo/text加载内联的字符串,例如:使用模板部件加载一个模板,你需要定义你自己的模块如下:
// in "my/widget/NavBar.js" define([ "dojo/_base/declare", "dijit/_WidgetBase", "dijit/_TemplatedMixin", "dojo/text!./templates/NavBar.html" ], function(declare, _WidgetBase, _TemplatedMixin, template){ return declare([_WidgetBase, _TemplatedMixin], { // template contains the content of the file "my/widget/templates/NavBar.html" templateString: template }); });
(2)dojo/i18n
dojo/i18n会根据浏览器的用户区域设置而加载不同的语言资源包。
// in "my/widget/Dialog.js" define([ "dojo/_base/declare", "dijit/Dialog", "dojo/i18n!./nls/common" ], function(declare, Dialog, i18n){ return declare(Dialog, { title: i18n.dialogTitle }); });
(3)dojo/has
Dojo的加载器包含了一套符合has.js定义的用于检测环境信息的API的实现代码,jojo/has插件就是用于有条件地为那些需要的模块完成这一功能。
// in "my/events.js" define([ "dojo/dom", "dojo/has!dom-addeventlistener?./events/w3c:./events/ie" ], function(dom, events){ // events is "my/events/w3c" if the "dom-addeventlistener" test was true, "my/events/ie" otherwise events.addEvent(dom.byId("foo"), "click", function(){ console.log("Foo clicked!"); }); });
(4)dojo/domReady
Dojo/domReady 是dojo/ready的替代品。这个表示只有当DOM初始化后才会执行模块。
// in "my/app.js" define(["dojo/dom", "dojo/domReady!"], function(dom){ // This function does not execute until the DOM is ready dom.byId("someElement"); });
注意:我们这里并没有在我们的回调函数中为dojo/domReady模块的返回值定义任何参数,这是因为它的返回值是没有用的。我们使用这个插件的目的只是延迟回调函数的执行。不需要使用返回值的那些模块或者插件应该添加在需要的依赖模块列表的尾部,这是因为这些模块和它们的局部变量的名称是按照顺序一一对应的。
尽管,数据没有被传递给domReady插件,但是“!”符号还是需要的。如果你不添加这个符号,那么你就仅仅是加载了dojo/domReady作为依赖模块,而不再起到这个插件本身的作用了。
参考文献:http://dojotoolkit.org/documentation/tutorials/1.9/modules/