原文:
http://dojotoolkit.org/documentation/tutorials/1.10/modules/
难度等级:初级 Dojo 版本:1.10
If you are migrating from a version of Dojo lower than 1.7, you may find the 1.8 version of this tutorial useful as it provides some guidance on migrating from Dojo's old module system to AMD. This tutorial will focus strictly on AMD.
概览
自 Dojo 1.7 以后,Dojo 采用异步模块定义(AMD)格式来定义模块。它比传统方式大为增强,包括全异步操作,真正的 package便携性,更好的依赖管理,增强的错误调试。他是由社区推动的规范,只要是按照 AMD 格式编写的模块就可以用于任何一种 AMD兼容的 loader 或库。本文我们会详细阐述 AMD,并讲解如何使用。
什么是 module?
一个 module 简单的说就是一个可以被引用的值。如果你的模块需要暴露一些数据或方法,那这些数据或方法必定就是,代表这个模块的一个对象的属性。实际开发中,我们一般不会为封装一个简单的变量而创建一个 module,如 var tinyModule = 'simple value',当然你要这样干也行。Modules 的主要价值在于模块化你的代码,将不同的功能拆分到不同的逻辑单元。比如说你要定义一个 person ,具有姓名地址,还有一些方法,那么你最好将其封装到一起。一个 module 就对应文件系统中的一个文件。
我该如何创建 module?
在 AMD中,创建一个模块需要将其注册到 loader 中。
什么是 loader? loader 就是一段 JavaScript 代码,用于处理模块定义和加载。当你加载 dojo.js 或 后,你就拥有了一个 loader。同 loader 交互使用这两个方法:require 和 define..
全局方法 define 可以让你在 loader 中注册一个 module 。看个简单例子:
非常简单,但运作良好-现在我们定义了一个 module ,其值为数字 5。
-
define({
-
library: 'dojo',
-
version: 1.10
-
});
上面例子更进一步,当模块加载,我们就得到了一个对象,包含2个属性。
-
define(function(){
-
var privateValue = 0;
-
return {
-
increment: function(){
-
privateValue++;
-
},
-
-
decrement: function(){
-
privateValue--;
-
},
-
-
getValue: function(){
-
return privateValue;
-
}
-
};
-
});
上面例子,我们将一个 function 传入 define。
该 function 会被执行,其返回值作为 module (引用)被 loader 保存下来。这段代码使用闭包创建了一个私有的变量 privateValue,它不能被外部代码直接访问,但可以通过返回的对象所拥有的方法进行访问,而这个返回对象将作为模块的值 (modules's value)。
-
译注:我们已经看到了三种调用 define 的方式,其实有四种:
-
-
1、define(5):直接传入一个标准数据类型,返回的就是该数据。
-
2、define({}):传入一个对象,返回的就是该对象。
-
3、define(function(){return {} }):传入一个 function ;对其求值,返回其结果。
-
4、define([],function(){}):同3类似,不过增加了依赖模块。
-
-
由此可见,define函数是非常灵活的,同时我们也可以大致了解到对于一个模块,其实没什么神秘的,我们不过运行这个 module,得到返回值,dojo loader 就会将返回值保存起来,再次遇到 require 同一个模块时,则直接调用这个返回值。
-
-
另外,一般情况下 define 返回值都是一个对象,有时我们需要返回一个构造函数。对于 dojo loader 不会管你返回的是什么,统统保存下来,调用者需要自己确定如何使用模块值。下面“模块中加载其他模块”一节有个很好的例子。
如何加载一个 module?
初学者需要了解如何标注 modules。要加载一个 module,你必须先将其标识出来。类似于其他语言 中的 module/package,一个 AMD module 由其路径和文件名构成。现在我们将前面的示例代码保存成如下文件:
让我们添加一个 loader ( Dojo ) 和 index.html (应用程序的入口点)。整体文件结构如下:
-
/
-
index.html
-
/dojo/
-
/app/
-
counter.js
index 页面内容如下(译注:下面代码中的 log 方法要你自己实现,或者直接使用 console.log):
-
<html>
-
<body>
-
<script src="dojo/dojo.js" data-dojo-config="async: true"></script>
-
<script>
-
require([
-
"app/counter"
-
], function(counter){
-
log(counter.getValue());
-
counter.increment();
-
log(counter.getValue());
-
counter.decrement();
-
log(counter.getValue());
-
});
-
</script>
-
</body>
-
</html>
让我们分析下内容:
-
app/counter.js 模块定义文件,调用后会往 loader 中注册一个module。注意这里我们定义的模块是一个指向对象的引用,而不是一个构造函数 - 这也意味着无论载入 module 多少次,我们得到的都是指向同一个对象的引用。一般情况下,模块会返回构造函数,但某些情况下,返回一个单件对象更为合适。
-
在文件系统中定位 module 的规则就是搜索 index.html 所在目录的子目录,而这个子目录又要与 AMD loader 目录同级(dojo/dojo.js),通过这条规则我们就不需要进行任何配置,loader 就可以知道 module id "app/counter" 是指向 app/counter.js 文件,然后将其加载,返回值注册为 module。
-
在 index.html 文件中,我们调用 require 来加载 "app/counter" 模块。我们也可以采用最简单的模块加载方式 require(["app/counter"])。这种方式适用于不需要模块返回值(模块引用),而仅仅是利用其副作用(比如扩充其他模块)。如果你需要模块返回值(模块引用),那就一定要提供回调函数。loader 会确保 module 正确加载完毕后,将模块作为参数传递给回调函数并调用。同其他函数一样,参数名可以任意取-参数名不一定非要同 module 名字一致。当然,作为最佳实践,建议参数命名尽量同 module 名字相近。
模块中加载其他模块- Modules Loading Modules
译注:这节主要讲 define 函数的第一个参数,用于设定依赖关系,因此翻译成模块中加载其他模块。
前面的例子非常简单,仅仅展示了 define 函数的基本用法。当我们的应用程序由一堆组织良好的 modules 构成时,那么必然存在模块间的依赖关系。define 函数可以自动加载依赖关系。依赖模块列表需要传递给 define 函数。
译注:以下为 app/dateManager.js 文件,也就是模块 'app/dateManager'。
-
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);
-
}
-
});
-
});
这个例子展示了 AMD 应用程序的更多特性:
-
多个依赖 - 依赖列表参数包含 "dojo/dom" 和一个假想的 "app/dateFormatter" 模块
-
返回一个构造器 - 这里我们给 module 取个合适的名字,比如 "app/DateManager"。其他代码要使用这个模块时,代码如下:
-
require([
-
"app/DateManager"
-
], function(DateManager){
-
var dm = new DateManager();
-
dm.showDate('dateElementId', new Date());
-
});
要开发 Dojo 应用,AMD 是你需要熟悉的第一个主题内容,declare 是第二个至关重要的函数。如果你还不熟悉dojo/_base/declare,赶快到这里学习:tutorial !
使用 plugins
除了常规 modules,AMD loader 还依功能特性划分了一类模块叫做 plugin。Plugins 用于扩展 loader 特性,而非简单的只是加载 AMD module。 Plugins 同常规 module 某种程度上类似,但在 module 识别符后面跟了一个"!",用于标识这是一个 plugin 。"!"后面的数据将直接传递给 plugin 进行处理。看些例子可能更有助于理解。Dojo 本身提供了几个缺省的 plugins;有四个最为重要的是 dojo/text, dojo/i18n,dojo/has 和 dojo/domReady。让我们逐一讲解。
dojo/text 用于从文件中加载字符串 (比如 HTML 模板)。加载值会被缓冲,下次如果有同一文件的请求,则不再发起网络请求。builder 会针对使用dojo/text 加载的字符串进行内联(inline)处理。示例,为一个模板化的 widget 加载 template,你可以如下定义 module:
-
// 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 变量已经包含了文件 "my/widget/templates/NavBar.html" 的内容
-
templateString: template
-
});
-
});
dojo/i18n 会根据浏览器的用户 locale 定义加载相关的语言资源包。其用法如下:
-
// 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
-
});
-
});
Dojo’s loader 包含一个 特性检测 API 的实现;dojo/has plugin 可用于动态调整依赖模块。用法大致如下:
-
// in "my/events.js"
-
define([
-
"dojo/dom",
-
"dojo/has!dom-addeventlistener?./events/w3c:./events/ie"
-
], function(dom, events){
-
// 如果 "dom-addeventlistener" 测试为 true,events 就是 "my/events/w3c"
-
// 否则 events 就是 "my/events/ie"
-
events.addEvent(dom.byId("foo"), "click", function(){
-
console.log("Foo clicked!");
-
});
-
});
dojo/domReady 是 dojo.ready 的替代版。他只会简单的阻塞直到 DOM 就绪后才正常返回。其用法如下:
-
// in "my/app.js"
-
define(["dojo/dom", "dojo/domReady!"], function(dom){
-
// 直到 DOM 就绪后,本函数才会执行。
-
dom.byId("someElement");
-
});
注意在回调函数中,我们没有为 dojo/domReady 定义任何对应的参数。这是因为返回值对我们来说没用--我们只是用它来延迟回调函数的执行而已。对于不需要其返回值的 modules 或 plugins ,应该将其放在依赖模块列表的末尾,因为回调函数中的参数同模块名是按照顺序一一对应的。
即便不需要往 plugin 传递参数,其后的感叹号还是需要的。如果没有它,dojo/domReady 就只会作为一个依赖模块被载入,而其作为 plugin 的特殊功能就不会启用。
小结
本教程讲解 的AMD 基础知识会带你入门,但随后还有更多的知识点。阅读 Advanced AMD Usage 教程来学习以下内容:
-
如果 loader 和 packages 存放位置不同,甚至放在不同的 server 上,该如何配置 loader
-
Creating packages of portable modules
-
Loading multiple versions of the same module or library
-
加载 non-AMD 代码
其他资源
阅读(3029) | 评论(0) | 转发(0) |