你可能没在使用Dojo, 或者在1.8 版本中依旧使用 1.6的代码而不知道如何继续。 你一直在听说 "AMD" 及 "baseleass", 但不知道如何去做或从哪开始。 本教程就是关于"AMD"以及Dojo的一些特性。
你可能没在使用Dojo, 或者在1.8 版本中依旧使用 1.6的代码而不知道如何继续。 你一直在听说 "AMD" 及 "baseleass", 但不知道如何去做或从哪开始。 本教程就是关于"AMD"以及Dojo的一些特性。
开始
从Dojo 1.7开始, Dojo Tookit 开始朝现代化架构转变。 在Dojo 1.8 继续了这种转变。 虽然它广泛的向后兼容(兼容1.6之前的版本), 但为了充分的利用 Dojo 1.8, 许多基础的理念发生了改变。 这些理念将形成Dojo 2.0 的基础。 所以为了直接充分的应用新的特性(例如 dojo/on), 你需要采用这些 ”现代“的理念。在整个教程中, 我会解释这些被Dojo引入的理念。 它们可能被划分为 "legacy" 和 "modern" Dojo. 我会尽我所能的解析为什么需要改变以及如何改变。 虽然有的是改变基本原来,看起来会让人困惑, 但它们会使你的代码更加有效, 运行更快, 更容易维护。 很值得你投入时间去理解 ”modern" Dojo.
这个教程不是一个迁移向导, 在这里讲解的都是基础的概念。 详细的迁移请参考 Dojo 1.x to 2.0 Migration Guide.
Hello New World
"modern" Dojo 核心概念之一是事物(变量或者方法)保存在全局命名空间中,这是非常不好的。 不好原因有很多种, 如在一个复杂的web应用程序中, 全局的命名空间会被各式各式的代码污染, 特别是在使用多个javascript框架的情况下。 在安全角度上,也需要考虑人们非法修改全局命名空间。 这个意思是在"modern" Dojo中, 如果你正打算从全局环境中访问某些事物, 请不要这样做。虽然此刻还有大量的toolkit为了兼容性的原因,大部分还是全局的。但这不应该出现在新的开发中。* 如果你发现你在使用 dojo.* 或者 digit.* 或 dojox.*, 这些都是错误的
这意味着虽然 开发者还在使用 "legacy" dojo. 仅仅包含 dojo.js, 获得基本的核心功能, 然后在请求其它的模块, 仅输入dojo.something 到你的核心内容(虽然在2.0之前会被广泛使用, 但这真的是一件非常非常糟糕的事情。)
自己多重得一样" 全局空间是不好的,全局空间是不好的。 我不在使用全局空间。 我不在使用全局空间”。
别外一个核心概念是同步操作是非常慢的,而异步操作更快。 “legacy" Dojo 虽然已经有了异步操作的概念 dojo.Deferred. 但是在”modern" Dojo中, 你应当认为任何操作都是异步的。
为了强化 Dojo的现代化和利用以上的概念(去全局化及异步操作), 在 1.7 中 Dojo 采用了CommonJS 模块定义被称为 Asynchronous Module Definition(AMD). 它重写的Dojo 模块加载器的基本原理, 通过暴露 require() 和 define() 函数来使用加载器。 你可以查看之前翻译的 dojo loader .
让我们看看之前在"legacy"中的例子:
dojo.ready(function(){ dojo.byId("helloworld").innerHTML = "Hello World!"; });
在 modern 中的版本
require(["dojo/dom", "dojo/domReady!"], function(dom){ dom.byId("helloworld").innerHTML = "Hello New World!"; });
"modern" dojo的基础就是require()函数。 它给javascript代码提供了闭包并且将需要相关工作的模块作为参数传递给require函数。 典型的, 第一个参数是一个模块IDs的(MIDs)数组, 第二个为一个函数。 在require()的闭包内。 我们引用的模块是基于在传递参数的变量名。 虽然我们可以调用一个模块的任何东西, 但也有一些规范需要遵守。
加载器, 就是传统的一样, 先是寻找模块,在加载和管理模块。
你可能已经注意到了,在依赖数组中有一个模块没有返回变量, dojo/domReady!. 它实际上加载一个“插件”,用来控制加载器的行为。 确保加载器在整个页面的 DOM结构都好后在执行回调函数。 在异步环境中, 不能够假设DOM结构已经好了,你可以操作DOM. 所以如果你想要在你的代码里操作DOM, 你必须加载domReady这个插件。 由于在回调函数内不需要使用到这个插件的返回值, 通常的惯例是将它放在数组的末尾。
你也可以在模块已经加载完后,通过 require()来获得这个模块的引用。 只需要给require传递一个MID 字符串参数。 它不会加载一个模块,如果一个模块没有被加载,会抛出一个错误。 你不会在Dojo Toolkit看到这种编程风格。 因为我们更希望在依赖数组里来管理模块。 如下是通过require来获得引用的方式:
require(["dojo/dom"], function(){ // some code var dom = require("dojo/dom"); // some more code });
Dojo 基础 和核心
你可能在处理"modern " Dojo时听说过 "baseless" 这个术语。 意思是确保一个模块不需要依赖任它不需要的 Dojo 基础功能。 在" legacy" 环境中, dojo.js 会有非常多的基础功能(在2.0之前都会存在)。 但为了更好的迁移, 你应该停止使用dojo.*.* dojoConfig的配置选项async. 默认为false, 它的意思是所有的 Dojo 基础模型会自动被加载。 如果你设置为true. 利用加载器的异步模式。 这些模块不会被加载。
此外, Dojo 包含 EcmaScript5的特性, 并有可能, 会摒弃在非现代化的浏览器下会模似ES5的特性的形为。这样,Dojo就不会适应所有的情形了。
虽然参考指南已经更新,会告诉你基础功能在哪。 你可以在 basic functions 找到相应的参考
基础及核心的所用,如下所示
dojo.require("dojo.string"); dojo.byId("someNode").innerHTML = dojo.string.trim(" I Like Trim Strings你现在可以使用如下方式
require(["dojo/dom", "dojo/string", "dojo/domReady!"], function(dom, string){ dom.byId("someNode").innerHTML = string.trim(" I Like Trim Strings "); });
事件与通知
虽然dojo.connect() 和 dojo.disconnect() 已经被移到dojo/_base/connect 模块中。 但 "modern" dojo 应该在使用dojo/on来处理事件和djojo/aspect来通知事件时应该遵循原有的模式。 更深层次的事件教程请参考 Events , 但在这我们要讲一些不同的东西。在 "legacy" Dojo中, 事件和修改方法行为没有很明显的区别, 通常dojo.connect() 可以用来处理两者。 事件是处理发生在一个对象上的相关事情,如点击事件。dojo/on 可以无缝的处理本地DOM事件和由Dojo对象或者部件窗口触发的事件。 在修改对象行为上, advice(通知)是面向 方面编程中的一个概念(术语),用来给接入点(或方法)添加额外的功能。大部分Dojo都尊循AOP规范,dojo/aspect模块集中展示了这种机集(体现AOP软件设计模式)。 --可以参考和了解下AOP,以及在Javascript中的实现。
在"leagacy" Dojo中,我们可能有很多种方法来处理按钮的onclick事件:
<script> dojo.require("dijit.form.Button"); myOnClick = function(evt){ console.log("I was clicked"); }; dojo.connect(dojo.byId("button3"), "onclick", myOnClick); </script> <body> <div> <button id="button1" type="button" onclick="myOnClick">Button1</button> <button id="button2" data-dojo-type="dijit.form.Button" type="button" data-dojo-props="onClick: myOnClick">Button2</button> <button id="button3" type="button">Button3</button> <button id="button4" data-dojo-type="dijit.form.Button" type="button"> <span>Button4</span> <script type="dojo/connect" data-dojo-event="onClick"> console.log("I was clicked"); </script> </div> </body>
在 "modern" Dojo中, 只能用 dojo/on来处理。 dojo/on可以在编程时或者button声明时指定,而不用提心处理的事件是DOM事件还是Digit/widget事件。
<script> require([ "dojo/dom", "dojo/on", "dojo/parser", "dijit/registry", "dijit/form/Button", "dojo/domReady!" ], function(dom, on, parser, registry){ var myClick = function(evt){ console.log("I was clicked"); }; parser.parse(); on(dom.byId("button1"), "click", myClick); on(registry.byId("button2"), "click", myClick); }); </script> <body> <div> <button id="button1" type="button">Button1</button> <button id="button2" data-dojo-type="dijit/form/Button" type="button">Button2</button> <button id="button3" data-dojo-type="dijit/form/Button" type="button"> <div>Button4</div> <script type="dojo/on" data-dojo-event="click"> console.log("I was clicked"); </script> </button> </div> </body>
* 注意digit.byId不能在使用。 在"modern" Dojo中, digit/registry 被用于窗口部件(widgets)中, registry.byId 来获得相应的窗口部件。需要注意的是dojo/on可以用来处理DOM 节点以及widget的事件。
给 "legacy" Dojo的方法添加功能,如下所示:
var callback = function(){ // ... }; var handle = dojo.connect(myInstance, "execute", callback); // ... dojo.disconnect(handle);
在 "modern" Dojo中, dojo/aspect模块允许你从一个方法中获得一个通知,并且给别一个方法添加 "before", "after" 或者 "around"等行为。 一般的,你可以用aspect.after() 代替dojo.connect()。如下:
require(["dojo/aspect"], function(aspect){ var callback = function(){ // ... }; var handle = aspect.after(myInstance, "execute", callback); // ... handle.remove(); });
* 阅读 dojo/aspect参考指南以及 David Walsh's 的博客 dojo/aspect 获得更多信息
Topics (发布与订阅专题)
另一个经过修改的领域是Dojo的 "publish/subscribe"功能。 现在已经在dojo/topic模块中实现并得到改进. 例如, "legacy" dojo的publish/subscribe 如下实现:// To publish a topic dojo.publish("some/topic", [1, 2, 3]); // To subscribe to a topic var handle = dojo.subscribe("some/topic", context, callback); // And to unsubscribe from a topic dojo.unsubscribe(handle);
在"modern" Dojo中,你需要利用dojo/topic模块来做相同的事情:
require(["dojo/topic"], function(topic){ // To publish a topic topic.publish("some/topic", 1, 2, 3); // To subscribe to a topic var handle = topic.subscribe("some/topic", function(arg1, arg2, arg3){ // ... }); // To unsubscribe from a topic handle.remove(); });
* 查看更多 dojo/topic 参考指南
* 注意现在的publish参数不在是一个数组。
Promises(承诺)
Dojo一直最核心的概念是Deferred类, 虽然在Dojo 1.5中变为了 "Promises" 的结构体系,但还是值得我们在此讨论。 另外, 在Dojo 1.8中, promise 的API已经被重写。 虽然跟之前的语义(功能)一样,但它不在支持 "legacy" API。 所以如果你想使用它, 你必须采用 "modern" API. 在 "legacy" Dojo 你可以如下使用 Deferred:function createMyDeferred(){ var myDeferred = new dojo.Deferred(); setTimeout(function(){ myDeferred.callback({ success: true }); }, 1000); return myDeferred; } var deferred = createMyDeferred(); deferred.addCallback(function(data){ console.log("Success: ", data); }); deferred.addErrback(function(err){ console.log("Error: ", err); });
"modern" Dojo如下使用:
require(["dojo/Deferred"], function(Deferred){ function createMyDeferred(){ var myDeferred = new Deferred(); setTimeout(function(){ myDeferred.resolve({ success: true }); }, 1000); return myDeferred; } var deferred = createMyDeferred(); deferred.then(function(data){ console.log("Success: ", data); }, function(err){ console.log("Error: ", err); }); });
* 关于更多 Deferreds(异步执行队列), 请查看 Getting started with Deferreds 教程。 关于Promises 请查看 Promises基础
* dojo/DeferredList 虽然还存在,但已被弃用。 你可以参考更强的 dojo/promise/all 和 dojo/promise/first
请求(requests)
任何一个javascript库中,其中核心的原理之一是AJAX. Dojo 1.8的Ajax基本构造API已被更新, 可跨平台运行, 易扩展, 提高代码的重用。 之前,你可能需要在XHR, Script, IFrame IO 通信中转来转去, 以及经常要自己处理外部返回的数据。 现在完全用dojo/request处理所有的问题。dojo/request 可以用原来的方式,就像 dojo/promies也有老的实现一样,但你也可以轻意的采用新的方式来重构你的代码, 如下是 "legacy"中的代码:
dojo.xhrGet({ url: "something.json", handleAs: "json", load: function(response){ console.log("response:", response); }, error: function(err){ console.log("error:", err); } });
"modern" Dojo 可以如下实现:
require(["dojo/request"], function(request){ request.get("something.json", { handleAs: "json" }).then(function(response){ console.log("response:", response); }, function(err){ console.log("error:", err); }); });
* dojo/request 会根据你的平台来加载最合适的请求处理方式, 对于浏览器来说一般是 XHR. 以上的代码可以轻易的在NodeJS中使用。 而你不需要修改任何代码。
这也是一个广泛讨论的话题, 可以查看 Ajax with dojo/request 教程来了解更多。
Document Object Model(DOM) 操作
如果你是从头开始阅读本教程,你可能发现了一些趋势, Dojo不仅抛弃了对全局空间而采用了新的模式, 它也将许多核心功能封装对模块中. 那么对于一个Javascript toolkit 来说, 什么还比DOM操作更核心的呢?还好,现在 DOM 操作也被分解为更多的模块。 以下是DOM操作的模块摘要信息:
isDescendant()
setSelectable() dojo/dom-attr DOM attribute functions has()
get()
set()
remove()
getNodeProp() dojo/dom-class DOM class functions contains()
add()
remove()
replace()
toggle() dojo/dom-construct DOM construction functions toDom()
place()
create()
empty()
destroy() dojo/dom-form Form handling functions fieldToObject()
toObject()
toQuery()
toJson() dojo/io-query String processing functions objectToQuery()
queryToObject() dojo/dom-geometry DOM geometry related functions position()
getMarginBox()
setMarginBox()
getContentBox()
setContentSize()
getPadExtents()
getBorderExtents()
getPadBorderExtents()
getMarginExtents()
isBodyLtr()
docScroll()
fixIeBiDiScrollLeft() dojo/dom-prop DOM property functions get()
set() dojo/dom-style DOM style functions getComputedStyle()
get()
set()
Dojo 工具包的访问器逻辑已被划分为多个,而不是全用原来的Dojo, 访问属性时使用dom-attr. "legacy" Dojo的代码如下所示:
var node = dojo.byId("someNode"); // Retrieves the value of the "value" DOM attribute var value = dojo.attr(node, "value"); // Sets the value of the "value" DOM attribute dojo.attr(node, "value", "something");
而 "modern" Dojo如下,需要指定两个依赖:
require(["dojo/dom", "dojo/dom-attr"], function(dom, domAttr){ var node = dom.byId("someNode"); // Retrieves the value of the "value" DOM attribute var value = domAttr.get(node, "value"); // Sets the value of the "value" DOM attribute domAttr.set(node, "value", "something"); });
在 "modern" 的例子中, 它非常的清楚你的代码需要什么, 但也增加或者缺少某个参数时,也会有意想不到的问题。 这种分离的访问器始终贯穿整个 "modern" Dojo.
DataStores 对比 Store
在 Dojo 1.6中, 引入了新的dojo/store API, 而弃用 dojo/data API. 虽然在 Dojo 2.0 之前还会保持 dojo/data 和dojox/data, 但最好还是将它们迁移到 dojo/store. 在这里我们不在详细讲述数据存储这个主题,但更多的信息可以查看 Dojo Object Sotre .
* 关于 dojo/store 可以查看 dojo/store 参考指南
Dijit 和 窗口部件(Widgets)
Dijit 自已在 "modern" Dojo中也发生了转变, 但大部分是在基础部分的改变, 在功能上被分划成独立的部件,然后在互相结合,完成更复杂的功能。 如果你正在创建一个自己的窗口部件, 你应该阅读一下 Creating a custom widget 指南 .如果你仅仅想在 digits或者其它的 Widgets的基础上开发, 那么需要介绍一下 dojo/Stateful 和 dojo/Evented 类。
dojo/Stateful 提供了一些独立的访问器来获得窗口部件的属性,并且能够监视这些属性的变化。 例如, 你可以做如下的事情:
require(["dijit/form/Button", "dojo/domReady!"], function(Button){ var button = new Button({ label: "A label" }, "someNode"); // Sets up a watch on button.label var handle = button.watch("label", function(attr, oldValue, newValue){ console.log("button." + attr + " changed from '" + oldValue + "' to '" + newValue + "'"); }); // Gets the current label var label = button.get("label"); console.log("button's current label: " + label); // This changes the value and should call the watch button.set("label", "A different label"); // This will stop watching button.label handle.unwatch(); button.set("label", "Even more different"); });
dojo/Evented 给类提供了一个 emit() 和 on() 函数, 并且做为Dijits和widgets的一部分。 可以使用widget.on()来设置事件处理。 如下所示:
require(["dijit/form/Button", "dojo/domReady!"], function(Button){ var button = new Button({ label: "Click Me!" }, "someNode"); // Sets the event handling for the button button.on("click", function(e){ console.log("I was clicked!", e); }); });
解析 (Parser)
最后来讲一下dojo/parser. Dojo 在代码编程(纯Javascript代码)和标签声名中都很有优势。 使用dojo/parser 可以解析标签声名并转变为一个实例对像和widgets. 所有上面提及到的 "modern"思维对dojo/parser很有影响, dojo/parser也有一些自己的现代化的转变。虽然 parseOnLoad: true 在 Dojo configuration 依赖支持。 但最好还是显示的调用parse方法,如:
require(["dojo/parser", "dojo/domReady!"], function(parser){ parser.parse(); });
Parser另一个“大”的改变是支持HTML5属性来标记节点。 它允许你的 HTML 标签可以通过HTML5的校验。 尤其是 dojoType 变成了data-dojo-type, 而不是指定的对象参数为无效的 HTML/XHTML属性,所有的参数会被传递给 data-dojo-props中指定的对象构造函数。
<button data-dojo-type="dijit/form/Button" tabIndex=2 data-dojo-props="iconClass: 'checkmark'">OK</button>
* Dojo 支持在data-dojo-type中使用 Module ID(MID)。 例如 dojoType="digit.form.Button" 变为 data-dojo-type="digit/form/Button"
对于上面提及到的dojo/Evented 和 dojo/Stateful, parser 也可以在声明角本中复制到相对应的功能, type 声明我 dojo/on 直复制"on"的功能,声明为 dojo/watch则复制"watch"的功能。 如下:
<button data-dojo-type="dijit/form/Button" type="button"> <span>Click</span> <script type="dojo/on" data-dojo-event="click" data-dojo-args="e"> console.log("I was clicked!", e); this.set("label", "Clicked!"); </script> <script type="dojo/watch" data-dojo-prop="label" data-dojo-args="prop, oldValue, newValue"> console.log("button: " + prop + " changed from '" + oldValue + "' to '" + newValue + "'"); </script> </button>
另外, parse也支持之前介绍过的 dojo/aspect, 你可以为 “before", "after" 和 "around"提供相应的代码。 查看 dojo/parser 参考指南获得更多信息。
Builder
最后一个是教程中暂时还没有接触的领域, Dojo Builder. 在 Dojo 1.7中, 它完全被重写了。 部分由于AMD的变化, 更重要是它被设计的更现代化,功能更丰富。 讲解Build 已经超出了该指南的范围。 你应该阅读 Creating Builds 指南来获得更多的信息。但在使用 "modern" builder之前,最好忘记旧的builder的版本。总结
希望你对 "modern" dojo有了兴趣, 虽然从"legacy"到 "modern"需要一段时间的适应。 但是一旦你适应了 "modern" dojo, 你就很难回到”leagacy" Dojo."modern" Dojo有以下特性:
- 颗粒化的依赖和模块化—— 仅当你需要什么的时候在加载, 对激进的现实主义(不管有用没有全部加载)说再见。 创建更快/更智能/安全的应用。
- 异步——事情没必要按给定的顺序发现,编码时多使用异步操作。
- 全局作用域是不好的—— 请一次次的跟着我念, "我不会使用全局作用域“
- 离散的访问器—— 一个功能只做一件事情, 特别访问器(accessors), 只有一个get() 和 set()方法。
- Dojo补充了ES5——如果EcmaScript 5在做某些事情,则Dojo不会在做。
- 事件和通知,而不是相连的——Dojo 已经从”一般”的连接迁移分别迁移到了事件和面向方面编程\
- Builder——它非常强大,功能更加丰富。