当前位置: 代码迷 >> Web前端 >> dojo1.7通译 定义模块(Defining Modules)
  详细解决方案

dojo1.7通译 定义模块(Defining Modules)

热度:569   发布时间:2012-08-22 09:50:35.0
dojo1.7翻译 定义模块(Defining Modules)

原文地址:http://dojotoolkit.org/documentation/tutorials/1.7/modules/

?

dojo现在支持在异步模块异步(AMD)定义中加入模块写入功能了,这使得代码更容易编写和调试。在这一节中,我们学习关于这个模块的使用,并探讨如何使用它。

概述

异步模块定义(AMD)格式是一种新的模块格式,只使用于1.7版本以上。它取代了dojo.provide, dojo.require, ?dojo.requireIf, ?dojo.requireAfterIf, dojo.platformRequire, and dojo.requireLocalization的这种写法。它提供了在模块的风格上许多的dojo改进,包括充分的异步操作,真正的包装可移植性,更好的依赖性管理和改善调试支持。它也是一个社区驱动的标准,这意味着模块写入的这种风格可用于任何其他适合AMD装载的模块。我们将给你演示怎样使用这个新的格式,以及强调一些对于旧风格新特性的优势。

?

介绍异步模块的定义

在讨论加载模块之前,我们先简单的介绍下模块是如何定义的。

如果你以前使用过dojo的1.6版本或更早的版本,你首先会发现最大的不同的地方是,新的模块定义看起来更像是文件的路径了。例如:以前的dojox.encoding.crypto.Blowfish 现在将写成dojox/encoding/crypto/Blowfish. 新的定义使你用起来更像是要处理文件路径。你可以使用"./"和"../"来相对引入在同一包下的其它模块。你甚至可以使用url来代替模块定义来加载模块。

在介绍完新特性后,现在我们就开始我们的教程了。

?

配置加载器

我们的dojo整体文件夹结构应该是和下面的一样

?

/
  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的设置也可以是在dofoConfig对象里设置,但不管怎么做,这个设置必须是在加载dojo之前设置。如果这个被忽略了,他就会使用同步操作。

在异步的模式下,加载器只定义了两种函数。

request 用于加载模块

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,避免混淆不清的行为习惯导致模块包装路径异常。最后是定义包列表,这里util没有被加到包里,虽然也访问util但使用器时对于加载的文件请求解析会有不同。

?

Packages

?

一般来说,包只是模块的集合。dojo,dijit,dojox都是包的实例。然而,不同的目录中的模块的简单集合,包还包含了一些额外的功能,这些功能显着增强了模块的可移植性和易于使用。

?

包有三个主要的配置选项。

name,是包的名字。

location 是包的位置,可以是相对路径的baseURL或绝对路径。

main 是一个可选参数,默认为“main”,如果有人试图要求加载模块,它可以用来发现加载正确的模块文件。例如,如果你尝试要求“dojo”,将装载的是实际的文件"/js/dojo/main.js"。既然我们已经为“我”包重写此属性,如果有人需要“my”,他们将载入“"/js/my/app.js"的。如果我们试图要求加载“util”,这是没有定义的包,装载机会尝试加载"/js/util.js".。

现在,我们已经正确配置加载器,让我们学习如何使用它,先来看require?

?

?

?

Requiring 模块

最好的解释就是例子

require([ "dojo/_base/declare", "dijit/_WidgetBase", "dijit/_TemplatedMixin" ], function(declare, _WidgetBase, _TemplatedMixin){
    // "declare" holds the dojo declare function
    // "_WidgetBase" holds the dijit _WidgetBase constructor
    // "_TemplatedMixin" holds the dijit _TemplatedMixin constructor
    // Do whatever you want with these modules here.
});
?正如你可以看到,require 功能需要两个参数。第一个参数是“依赖关系”的一个集合,第二个参数是一个回调函数。通过列出的顺序解决了模块的依赖。一旦解决了所有的依赖,他们就会传递给回调函数。回调函数是可选的,所以如果你没有做任何功能。只是要加载一些模块,你可以简单地忽略它。如果忽略依赖关系则意为着是另一种操作方案,所以一定要保证有一个依赖在里面,即使它是空的。

require 功能也可用于在运行时重新配置装载机在配置对象时作为第一个参数传递进去
require({
    baseUrl: "/js/",
    packages: [
        { name: "dojo", location: "//ajax.googleapis.com/ajax/libs/dojo/1.7.1/" },
        { name: "my", location: "my" }
    ]
}, [ "my/app" ]);
?
在这里,我们已经改变了配置,定义dojo包到一个Google的CDN地址。不同于传统的加载模块格式,在异步AMD的格式中是隐含支持跨域加载的。

请注意,并非所有的配置选项是可以在运行时设置。特别是async,tlmSiblingOfDojo,和has,一旦加载器加载后是不能改变的。此外,大多数的配置数据是浅复制,这意味着你不能使用这个机制,例如,添加自定义配置对象将会被覆盖

Defining 模块
定义模块使用define 函数完成。define也调用require,但回调的返回值保存的新定义的模块。
// in "my/_TemplatedWidget.js"
define([ "dojo/_base/declare", "dijit/_WidgetBase", "dijit/_TemplatedMixin" ], function(declare, _WidgetBase, _TemplatedMixin){
    return declare([ _WidgetBase, _TemplatedMixin ], {});
});
?上面的例子,我们通过dojo.declare创建和返回模块。定义模块时要注意的一件重要的事情是,回调函数只调用一次返回值并由装载器缓存。在实践层面,这意味着模块可以很容易地共享对象。

相对应的,使用以前的版本的代码看起来像这样:
dojo.provide("my._TemplatedWidget");
dojo.require("dijit._WidgetBase");
dojo.require("dijit._TemplatedMixin");
dojo.declare("my._TemplatedWidget", [ dijit._WidgetBase, dijit._TemplatedMixin ], {});
?当定义一个模块时,也可以把值定义为一个对象
// in "my/nls/common.js"
define({
    greeting: "Hello!",
    howAreYou: "How are you?"
});
?请记住,如果你定义一个模块,而没有使用一个回调函数,你将不能够引用任何依赖,因此这种类型的定义是不常用的,通常只是在取国际化时使用。

使用快捷式模块
新的异步加载机最重要的特点之一是能够创建完全快捷式模块。举例来说,如果你有一个应用程序,需要使用两个不同版本的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属性也有可能重新映射整个路径。路径匹配是从任何一个模块的标识符字符串的开头开始的部分开始,选用最长匹配的路径。例如:
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只匹配完整的模块标识符。别名还递归匹配的别名,直到没有新的匹配结果。例如
var dojoConfig = {
    aliases: [
        [ "text", "dojo/text" ],
        [ "dojo/text", "my/text" ],
        [ "i18n", "dojo/i18n" ],
        [ /.*\/env$/, "my/env" ]
    ]
};
?
对应的解析结果
text => my/text
dojo/text => my/text
i18n => dojo/i18n
foo => foo
[anything]/env => my/env
使用别名时,目标别名必须是绝对的模块标识符,源别名必须是绝对的模块标识符或正则表达式。

编写可移植模块
在装载机为了能够执行所有的可移植性,任何包内的模块参考使用相对模块标识符,这一点很重要。因此,举例来说,下面的代码
// in "my/foo/blah.js"
define([ "my/otherModule", "my/foo/bar" ], function(otherModule, bar){
    // …
});
?改成为相对路径的代码
// in "my/foo/blah.js"
define([ "../otherModule", "./bar" ], function(otherModule, bar){
    // …
});
?记住相对标识符只能用来指向在相同的包中的模块

引入有条件的模块
有时候,你可能想需要一个符合某些条件的模块。例如,你可能要延迟加载一个可选模块。如果你使用明确路径的模块定义这是非常简单的:
// in "my/debug.js"
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);
        });
    });
});
?不幸的是,想要完全可移植,"my/debug/console"必须转向到一个相对标识符。只是改变位置路径值是不行的,因为原始模块中已经require过了。为了解决这个问题,dojo加载器提供了一种叫做上下文敏感的require。为了使用其中之一,在初始化定义时,就要通过特殊的模块标识符“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(evt){
        require([ "./debug/console" ], function(console){
            domConstruct.place(console, document.body);
        });
    });
});
?现在我们就可以使用require去关联到my/debug了

使用插件
异步加载器在除了加载原有模块外还可以加载一个新的模块类型,称为插件。使用插件来扩展新的功能。作为一个经常性的插件加载模块是和原有模块使用同样的方式,但是在模块标识符中使用!来标识这是以一个插件请求的require.将直接通过插件进行处理。这将变得更加清晰,我们看几个例子。 Dojo有一些默认插件;五个最重要的是dojo/text, dojo/i18n, dojo/has, dojo/load, 和 dojo/domReady。让我们来看看他们是如何使用。

dojo/text
dojo/text是更换dojo.cache,用来需要从一个文件(如HTML模板)加载一个字符串。例如,加载一个模板部件的模板,你会这样定义你的模块
// in "my/Dialog.js"
define([ "dojo/_base/declare", "dijit/Dialog", "dojo/text!./templates/Dialog.html" ], function(declare, Dialog, template){
    return declare(Dialog, {
        templateString: template // template contains the content of the file "my/templates/Dialog.html"
    });
});
?相对应于以前的版本
dojo.provide("my.Dialog");
dojo.require("dijit.Dialog");
dojo.declare("my.Dialog", dijit.Dialog, {
    templateString: dojo.cache("my", "templates/Dialog.html")
});
?
dojo/i18n
dojo/i18n替代了dojo.requireLocalization和dojo.i18n.getLocalization
// in "my/Dialog.js"
define([ "dojo/_base/declare", "dijit/Dialog", "dojo/i18n!./nls/common"], function(declare, Dialog, i18n){
    return declare(Dialog, {
        title: i18n.dialogTitle
    });
});
?相对应于以前的版本代码
dojo.provide("my.Dialog");
dojo.require("dijit.Dialog");
dojo.requireLocalization("my", "common");
dojo.declare("my.Dialog", dijit.Dialog, {
    title: dojo.i18n.getLocalization("my", "common").dialogTitle
});
?
dojo/has
新加载器实现加载了has.js功能
// 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(evt){
        console.log("Foo clicked!");
    });
});
??相对应于以前的版本代码
dojo.requireIf(window.addEventListener, "my.events.w3c");
dojo.requireIf(!window.addEventListener, "my.events.ie");
my.events.addEvent(dom.byId("foo"), "click", function(evt){
    console.log("Foo clicked!");
});
?
dojo/load
dojo/load和dojo/has很相似,但dojo/load使用了两次模块定义并返回模块加载集合
// in my/loadRenderer.js
define([ "dojo/load!./sniffRenderer" ], function (renderer) {
   // do something with renderer
});
 
// in my/sniffRenderer.js
define([], function () {
  // returns an array of module identifiers to load;
  // the first module listed is the one returned by dojo/load
  return [ sniffModuleIdOfRenderer() ];
});
?但有多个模块被定义时,只取第一个模块。如果没有模块被定义,将标识undefined
? 相对应于以前的版本代码
var module = sniffModuleIdOfRenderer();
dojo.require(module);
var renderer = dojo.getObject(module);
// do something with renderer
?
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.ready(function(){
    dojo.byId("someElement");
});

?处理循环依赖
当你写代码时,你可能偶尔会遇到的情况,你有两个模块,需要互相引用,此引用就形成了循环的依赖。为了解决这样一个循环依赖,加载器在第一次递归时就立即解决问题,。例如,考虑以下的例子
// in "my/a.js"
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的值到模块B,然后模块A的回调函数将被调用,其返回值被丢弃。在上面的例子中,这意味着将是一个空的对象,而不是一个功能的对象,所以我们的代码没有如预期般运作。

为了解决这个问题,加载器提供了一个特殊的“ exports?”模块标识符。使用时,该模块将返回空对象被用来解决循环依赖。回调被调用时,它可以附加属性出口。这种方式,功能仍然可以成功地定义和使用:
// in "my/a.js"
define([ "b", "exports" ], function(b, exports){
    exports.stuff = function(){
        return b.useStuff ? "stuff" : "things";
    };
     
    return exports;
});
 
// in "my/b.js"
define([ "a" ], function(a){
    return {
        useStuff: true
    };
});
 
// in "my/circularDependency.js"
require([ "a" ], function(a){
    a.stuff(); // "stuff"
});
?

请记住,虽然我们已经成功地解决了这两个模块的循环依赖问题,但这仍是一个相当不稳定的。因为我们没有更新模块B??,如果是先要加载B而不是A的话又会出现循环依赖的问题。在这种情况下,它最终将被定义为一个空的对象。此外,如果模块A需要返回一个函数,而不是一个对象,使用"exports" ,将无法正常工作。对于这些原因,只要有可能,代码应该重构,以消除循环依赖。

加载非异步的代码
模块标识符部分中提到,异步加载器还可以通过一个标识符用来加载非异步的代码,实际上是一个JavaScript文件的路径。装载机在以下三种方式之一确定这些特殊的标识符:

标识符开始“/”
标识符开始的协议(例如“HTTP:”,“HTTPS:”)
标识符结束“.js”
当任意代码作为一个模块加载,模块的值是undefined的,您将需要直接访问代码脚本的全局定义。

最后一个功能是dojo加载器能够混合和匹配与异步式模块遗留下来的Dojo模块。这使得它可以从一个传统的codebase AMD的代码库,慢慢地,有条不紊地过渡。而不需要立即转换一切。当在异步模式下,解决遗留模块的值对象,是在全局范围内使用dojo.provide调用。例如:
// in "my/legacyModule.js"
dojo.provide("my.legacyModule");
my.legacyModule = {
    isLegacy: true
};
?当异步加载器加载到require(["my/legacyModule"]),处理的方式是把对象赋值于my.logacyModule

服务端脚本
最后的一个新功能是新异步加载器所拥有的使用node.js或Rhino加载服务器脚本。
# 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>
?
结论
dojo的异步加载器提供了更多的功能和特性,虽然代码相对有点长。这里只是简短的介绍,想要了解更多内容请查看参考文档。

?

  相关解决方案