http://dojotoolkit.org/documentation/tutorials/1.10/modern_dojo/index.htmljavascript
你可能已经不用doio一段时间了,或者你一直想保持你基于dojo1.6的老代码在1.10下可以正确运行,但殊不知道应该怎么作。你一直在据说“AMD”和"baseless"等概念,但殊不知道如何开始学习这些,这篇教程会帮你了解这些。html
从dojo的1.7版本开始,dojo Toolkit有了重大的变化,并朝着更加先进的架构模式发展。dojo1.10延续了这样的发展路线。虽然dojo1.10是向后兼容的,但为了充分发挥dojo1.10的优点,一些概念也发生了变化。这些概念将成为dojo2.0的基础,因此你必须确保你正在正确的学习道路上。因此了可以充分的利用如今dojo的一些新特性,例如dojo/on,你必须理解一些先进的概念。java
在本教程中,咱们会想你介绍dojo新加入的一些概念。我会说起到废弃的和先进的dojo。我会尽我最大的努力介绍哪些东西发生了变化,为何变化。一些变化是基本的变化,但也有一些变化时使人难以理解的,不过他们都是为了让你的代码更有效率,运行的更快,更好的利用Javascript,让你的代码有更好的可维护性。我以为花一些时间去了解现代的dojo是十分值得的。node
本教程并非一个dojo变化迁移指南,但不少时候会使你想到你已经熟悉的dojo。一些技术细节能够在该教程获取,Dojo 1.X to 2.0 Migration Guide。编程
现代dojo的一个核心概念就是全局命名是不可取的。获得这样的结论是有不少缘由的,但在一个复杂的Web应用中,全局命名的变量很容易和各类代码混杂在一块儿,特别是使用了多个js框架的时候。站在安全的立场上,我不得不说起,有些不法分子甚至恶意的修改全局变量名称。现代dojo的解决办法是若是你想直接访问全局命名空间,讲会被禁止,由于你正在作一件错误的事情。虽然鉴于向后兼容的缘由,绝大多数Toolkit仍然保存着全局命名,但在之后的开发中国不要再使用这种方式了。json
若是你发现你的代码中抱哈了dojo.*、dijit.*或者dojox.*,那么你已经作错了一些事情。数组
这意味着尽管开发人员还可使用过期的dojo,例如包含dojo.js,调用一些核心的功能,加载一些模块,定义自定义模块等,但咱们是在不推荐你这些继续作下去了。事实是,这些性能在2.0版本以后都不在支持。promise
Again, repeat after me "the global namespace is bad, the global namespace is bad, I will not use the global namespace, I will not use the global namespace".浏览器
另外一个核心概念是,同步操做较慢,异步操做较快。之前的dojo已经使用dojo.Deferred
实现了比较强大的异步加载机制,但在现代dojo中,咱们想最好是全部的操做都是异步的。安全
To strengthen the modularity of Dojo and leverage the concepts above, in 1.7 Dojo adopted the CommonJS module definition called Asynchronous Module Definition (AMD). This meant a fundamental re-write of the Dojo module loader which is usually exposed through the require()
and define()
functions. You can find full documentation of the loader in the reference guide. This has fundamentally changed the way code is structured.
利用上面的概念,咱们能够增强dojo的模块化。在dojo1.7版本时,dojo引进了CommonJS定义的模型模块化概念,叫作异步模块定义(AMD)。这意味着以前加载和定义模块的函数requires()和define()函数都要须要重写。你能够查看到关于loader in the reference guide的完整文档资料。这重根本上改变了代码的结构。
Let's take for example something we would have done in "legacy":
下面咱们是使用过期的dojo方式写的一个例子。
1 dojo.ready(function(){ 2 dojo.byId("helloworld").innerHTML = "Hello World!"; 3 });
下面咱们再看下如今dojo的定义方式:
1 require(["dojo/dom", "dojo/domReady!"], function(dom){ 2 dom.byId("helloworld").innerHTML = "Hello New World!"; 3 });
欢迎来到完美的新世界,现代dojo的基础是require()函数。该函数建立了一个javascript闭包,加载代码须要的模块,并做为参数传递给主函数。函数的第一个参数是模块标识集合,第二个参数是主函数。在require()闭包中,咱们能够经过声明的参数来引用dojo模块。咱们调用模块时,存在一些经常使用的用法习惯。
The loader, just like the legacy one, does all the heavy lifting of finding the module, loading it and managing the loaded modules.
加载器能够查找模块,架子啊模块以及管理已经加载的模块等重要工做。
你能够看到,在上面的代码中,dojo/domReady!模块虽然在模块加载的数组中存在,但在参数的列表中却没有。其实这是一个加载器插件,用来控制加载器的行为。该插件的做用是加载等待一直等到在页面上加载的DOM结构都已经加载完毕。在异步状况下,你不可能尚未等DOM加载完毕,就去操纵它。因此若是你想对DOM节点作些操做,请确保使用了该插件。由于咱们不直接调用该插件的任何功能,并且该插件是模型标识集合中的最后一个,因此在主函数中,能够不明确设置该参数。
You can also get a reference to a module with require()
after that module has already loaded, by just supplying the MID as a single string argument. This won't load the module and will throw an error if it isn't already loaded. You won't see this coding style in the Dojo Toolkit, because we like to manage all of our dependencies centrally in the code. But if you choose to use this alternative syntax it would look something like this:
你也可使用require()函数加载一个模块,但不用把模块做为参数传递给主函数。若是没有加载该模块就直接调用,将会发生错误。你不会在Toolkit看到这种代码风格,由于咱们通常都会集中管理咱们编写代码的依赖。但若是你非要采用这种方式话,编写的代码以下面的代码所示:
1 require(["dojo/dom"], function(){ 2 // some code 3 var dom = require("dojo/dom"); 4 // some more code 5 });
当你使用现代Dojo的时候,可能据说过"baseless"的概念。这个概念保证了一个模块不会依赖任何一个其不须要的其余模块。在以前的版本中(dojo1.6及之前),会加载全部的功能,知道2.0版本以后。可是若是你想确保你的代码能够很方便的移植到新版本上,你必须中止使用dojo.*这样的用法。This does mean you might not know where certain parts of the namespace are now。
One of the dojoConfig
options is async
. It defaults to false
and this means all the Dojo base models are automatically loaded. If you set it to true
and take advantage of the asynchronous nature of the loader, these modules will not automatically be loaded. All of this combined together makes for a more responsive and faster loading application.
dojoConfig包含了一个名为async的属性。该属性的默认值为False,意味着全部的dojo基础模块都会被默认加载。若是把该属性这只为True,利用异步加载器,那么这些模型就不会自动的被加载。这种方式会让你的应用响应和加载更为迅速。
另外,dojo支持EcmaScript 5规则。在尽量的状况下,DOjo会支持ES5的一些功能,但在一些老旧的浏览器环境下,dojo可能模拟es5的功能。也就是说看起来像是dojo完成的功能,实际上可能不是dojo作的。
参考指南已经列出了全部的功能,里面也包含了basic functions。
Once you get outside of the Dojo Base and Core, almost everything else would work like the following. Where you would have done a dojo.require()
:
一旦你在dojo基础和核心以外编写代码,你可能会编写下面的代码。你会使用dojo.require()函数。
1 dojo.require("dojo.string"); 2 dojo.byId("someNode").innerHTML = dojo.string.trim(" I Like Trim Strings ");
但如今,你能够直接使用require()函数。
1 require(["dojo/dom", "dojo/string", "dojo/domReady!"], function(dom, string){ 2 dom.byId("someNode").innerHTML = string.trim(" I Like Trim Strings "); 3 });
dojo.connection()和dojo.disconnection()已经被移动大dojo/_base/connect模块中,如今dojo必须使用dojo/on来处理事件,使用dojo/aspect来通知函数。在Events教程中有更详细的信息,但咱们在这里主要对比他们之间的差异。在之前的dojo中,在事件和函数通知上,没有太大的区别,咱们使用使用dojo.connect()处理这两种状况。事件是当一个对象触发一个动做时关联的哈数,例如click事件。dojo/on能够无缝的处理原生态的DOM事件以及dojo小部件暴露出的事件。把一个行位连接到已有对象上也是AOP(面向方面编程)重要概念的一部分。不少dojo部分经过dojo/sapect模块提供的功能相应了AOP的思想。
在之前的dojo中,咱们处理Button的Onclick事件的代码以下:
1 <script> 2 dojo.require("dijit.form.Button"); 3 4 myOnClick = function(evt){ 5 console.log("I was clicked"); 6 }; 7 8 dojo.connect(dojo.byId("button3"), "onclick", myOnClick); 9 </script> 10 <body> 11 <div> 12 <button id="button1" type="button" onclick="myOnClick">Button1</button> 13 <button id="button2" data-dojo-type="dijit.form.Button" type="button" 14 data-dojo-props="onClick: myOnClick">Button2</button> 15 <button id="button3" type="button">Button3</button> 16 <button id="button4" data-dojo-type="dijit.form.Button" type="button"> 17 <span>Button4</span> 18 <script type="dojo/connect" data-dojo-event="onClick"> 19 console.log("I was clicked"); 20 </script> 21 </div> 22 </body>
在现代的dojo中,你只须要使用dojo/on模块就能够实现上述功能。不管是编程的方式仍是声明的方式,不管是原生态DOM事件仍是小部件定义的事件。
1 <script> 2 require([ 3 "dojo/dom", 4 "dojo/on", 5 "dojo/parser", 6 "dijit/registry", 7 "dijit/form/Button", 8 "dojo/domReady!" 9 ], function(dom, on, parser, registry){ 10 var myClick = function(evt){ 11 console.log("I was clicked"); 12 }; 13 14 parser.parse(); 15 16 on(dom.byId("button1"), "click", myClick); 17 on(registry.byId("button2"), "click", myClick); 18 }); 19 </script> 20 <body> 21 <div> 22 <button id="button1" type="button">Button1</button> 23 <button id="button2" data-dojo-type="dijit/form/Button" type="button">Button2</button> 24 <button id="button3" data-dojo-type="dijit/form/Button" type="button"> 25 <div>Button4</div> 26 <script type="dojo/on" data-dojo-event="click"> 27 console.log("I was clicked"); 28 </script> 29 </button> 30 </div> 31 </body>
Notice how dijit.byId
isn't used. In "modern" Dojo, the dijit/registry
is used for widgets and registry.byId()
retrieves a reference to the widget. Also, notice how dojo/on
handles both the DOM node and widget events in the same way.
你会先发现咱们没有用到dijit.byId函数。在现代dojo中,已经使用dijit/refistry模块中的registry.byId()函数来获取小部件的引用。而且使用dojo/on模块使用同一种方式处理原生态DOM和小部件的事件。
若是要解除一个事件绑定的话,使用之前的dojo,你可能会编写下面的代码:
1 var callback = function(){ 2 // ... 3 }; 4 var handle = dojo.connect(myInstance, "execute", callback); 5 // ... 6 dojo.disconnect(handle);
In "modern" Dojo, the dojo/aspect
module allows you to get advice from a method and add behaviour "before", "after" or "around" another method. Typically, if you were converting a dojo.connect()
you would replace it with an aspect.after()
which would look something like this:
在现代dojo中,使用dojo/aspect模块,容许你获得一个函数运行以前,以后的行为引用。你可使用aspect.after替换dojo.connect(),代码以下所示:
1 require(["dojo/aspect"], function(aspect){ 2 var callback = function(){ 3 // ... 4 }; 5 var handle = aspect.after(myInstance, "execute", callback); 6 // ... 7 handle.remove(); 8 });
Another area that has undergone a bit of a revision is the "publish/subscribe" functionality in Dojo. This has been modularized under the dojo/topic
module and improved.
For example, the "legacy" way of doing this would be something like:
另一个有重大修订的模块时发布和订阅模块。该模块已经被整合到dojo/topic模块下。例如在之前的代码中,咱们会写下面的代码:
// 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);
但为现代dojo中,你须要利用dojo/topic,按照下面的方式作。
1 require(["dojo/topic"], function(topic){ 2 // To publish a topic 3 topic.publish("some/topic", 1, 2, 3); 4 5 // To subscribe to a topic 6 var handle = topic.subscribe("some/topic", function(arg1, arg2, arg3){ 7 // ... 8 }); 9 10 // To unsubscribe from a topic 11 handle.remove(); 12 });
dojo中,一个最重要的核心概念之一就是延迟类,在1.5版本中,该功能已经从迁移到“promise”中,它值得咱们在这里讨论一下。在1.8版本时,promise类被重写。虽然重表面的语义上看,和之前同样,但该模块已经不在支持老旧的API,因此如何想使用该模块,必须使用现代Dojo的API。在老旧API中,实现相关功能的代码以下:
1 function createMyDeferred(){ 2 var myDeferred = new dojo.Deferred(); 3 setTimeout(function(){ 4 myDeferred.callback({ success: true }); 5 }, 1000); 6 return myDeferred; 7 } 8 9 var deferred = createMyDeferred(); 10 deferred.addCallback(function(data){ 11 console.log("Success: " + data); 12 }); 13 deferred.addErrback(function(err){ 14 console.log("Error: " + err); 15 });
但在现代dojo中,实现的代码是这样的
1 require(["dojo/Deferred"], function(Deferred){ 2 function createMyDeferred(){ 3 var myDeferred = new Deferred(); 4 setTimeout(function(){ 5 myDeferred.resolve({ success: true }); 6 }, 1000); 7 return myDeferred; 8 } 9 10 var deferred = createMyDeferred(); 11 deferred.then(function(data){ 12 console.log("Success: " + data); 13 }, function(err){ 14 console.log("Error: " + err); 15 }); 16 });
Ajax是每一个javascript库的核心模块之一。从dojo1.8版本以后,该模块的API也进行了更新,能够跨平台、易扩展并易重用。之前,你常常兼顾处理来自XHR、脚本以及IFrameIO以及本身的程序返回的数据。dojo/request模块就可让这些事情变得更简单。
Just like dojo/promise
the old implementations are still there, but you can easily re-factor your code to take advantage of the new. For example, in "legacy" Dojo you might have written something like this:
相似于dojo/promise
,之前的代码仍然可用,但你能够很难容易的重构你的代码。例如,使用之前的dojo,你写的代码以下所示。
1 dojo.xhrGet({ 2 url: "something.json", 3 handleAs: "json", 4 load: function(response){ 5 console.log("response:", response); 6 }, 7 error: function(err){ 8 console.log("error:", err); 9 } 10 });
使用现代Dojo,代码以下:
1 require(["dojo/request"], function(request){ 2 request.get("something.json", { 3 handleAs: "json" 4 }).then(function(response){ 5 console.log("response:", response); 6 }, function(err){ 7 console.log("error:", err); 8 }); 9 });
You might be seeing a trend here if you have gotten this far in the tutorial, in that not only has Dojo abandoned its dependency on the global namespace and adopted some new patterns, it has also broken out some of "core" functionality into modules and what is more core to a JavaScript toolkit than DOM manipulation.
在整个教程中,您能够看到了dojo的整个发展趋势,不只仅是把全局命名废弃,更重要的是基于模块化架构定义不少核心模块。例如对DOM的操做模块。
Well, that too has been broken up into much smaller chunks and modularized. Here is summary of the modules and what they contain:
DOM操做模块被分解为不少下的模块,主要的模块以下表所示:
Module
描述 | 包含的主要函数 | |
---|---|---|
dojo/dom | Core DOM functions | byId() 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的设计思路,那就是工具包的业务逻辑和访问是分离的。
以下面的代码所示:
1 var node = dojo.byId("someNode"); 2 3 // Retrieves the value of the "value" DOM attribute 4 var value = dojo.attr(node, "value"); 5 6 // Sets the value of the "value" DOM attribute 7 dojo.attr(node, "value", "something");
现代DOJO实现上面的功能代码:
1 require(["dojo/dom", "dojo/dom-attr"], function(dom, domAttr){ 2 var node = dom.byId("someNode"); 3 4 // Retrieves the value of the "value" DOM attribute 5 var value = domAttr.get(node, "value"); 6 7 // Sets the value of the "value" DOM attribute 8 domAttr.set(node, "value", "something"); 9 });
在这个现代dojo的例子中,很是清晰的展现了代码所作的工做。由于没有响应的参数传入,你编写的代码很难运行出不是你本意的效果。这种访问方式隔离机制一直贯穿这个现代dojo的设计思想。
在dojo1.6时,新的API dojo/store被加入,而dojo/data API被废弃。但dojo/data的数据存储以及dojox/data数据存储相关的功能一直在dojo2.0以前均可以使用。但咱们建议仍是使用的新的API。本教程不能展开详细的介绍该概念,但你能够在Dojo Object Store教程中查看更详细的信息。
dijit为了适应如今dojo世界,也进行了一些重构。但不少重构都已经在toolkit包的基础部分完成,主要的重构目标是把功能分解成一个个小的积木,而后再粘合在一块儿,完成复杂的功能。若是你正在建立一个自定义的小部件,你能够阅读Creating a custom widget教程。
If you are just developing with dijits or other widgets, then there were a few core concepts that were introduced with the dojo/Stateful
and dojo/Evented
classes.
dojo/Stateful
provides discrete accessors for widget attributes as well as the ability to "watch" changes to these attributes. For example, you can do the following:
若是你只是使用dijits或者其余的小部件进行开发,这儿有一些核心的概念须要介绍一下,dojo/Stateful
and dojo/Evented
。
dojo/Stateful模块提供了访问小布家眷性的接口,而且能够监测小部件属性的变化,例如:
1 require(["dijit/form/Button", "dojo/domReady!"], function(Button){ 2 var button = new Button({ 3 label: "A label" 4 }, "someNode"); 5 6 // Sets up a watch on button.label 7 var handle = button.watch("label", function(attr, oldValue, newValue){ 8 console.log("button." + attr + " changed from '" + oldValue + "' to '" + newValue + "'"); 9 }); 10 11 // Gets the current label 12 var label = button.get("label"); 13 console.log("button's current label: " + label); 14 15 // This changes the value and should call the watch 16 button.set("label", "A different label"); 17 18 // This will stop watching button.label 19 handle.unwatch(); 20 21 button.set("label", "Even more different"); 22 });
dojo/Evented
provides emit()
and on()
functionality for classes and this is incorporated into Dijits and widgets. In particular, it is "modern" to use widget.on()
to set your event handling. For example, you can do the following:
dojo/Evented为对象提供了emit()和on()函数,并让着两个函数成为dijits和小部件的一部分。在现代dojo代码中,会使用widget.on()函数去处理事件。例以下面的代码:
1 require(["dijit/form/Button", "dojo/domReady!"], function(Button){ 2 var button = new Button({ 3 label: "Click Me!" 4 }, "someNode"); 5 6 // Sets the event handling for the button 7 button.on("click", function(e){ 8 console.log("I was clicked!", e); 9 }); 10 });
最后是dojo/parser。dojo包含了编码式和声明式两种应用方式。使用dojo/paeser就能够把声明式的代码转换成dojo组件或对象。现代dojo的一些思想也已经对该组件产生了影响,并进行了重构。
doji仍然支持parseOnLoad:true这个配置,但更多的时候会有更明确的定义方式,例如:
1 require(["dojo/parser", "dojo/domReady!"], function(parser){ 2 parser.parse(); 3 });
dojo/parser模块的一个重大变化就是支持HTML5,并容许你标记HTML5节点。特别是把dojoType属性修改成data-dojo-type,而且代替非标准html的属性数据。全部的这些参数都会被传递给对象的构造函数。例如:
1 <button data-dojo-type="dijit/form/Button" tabIndex=2 2 data-dojo-props="iconClass: 'checkmark'">OK</button>
dojo支持在data-dojo-type属性上设置模块的ID。例如dojoType="dijit.form.Button"
变成了data-dojo-type="dijit/form/Button"。
在上述的dojo/Evented和dojo/Stateful的变化中有Watch和on函数功能,目前daojo/parser也已经响应了这些变化。例如:
1 <button data-dojo-type="dijit/form/Button" type="button"> 2 <span>Click</span> 3 <script type="dojo/on" data-dojo-event="click" data-dojo-args="e"> 4 console.log("I was clicked!", e); 5 this.set("label", "Clicked!"); 6 </script> 7 <script type="dojo/watch" data-dojo-prop="label" data-dojo-args="prop, oldValue, newValue"> 8 console.log("button: " + prop + " changed from '" + oldValue + "' to '" + newValue + "'"); 9 </script> 10 </button>
另外,parser也支持dojo/asoect的相关概念,因此你能够在上面的代码中使用Before、after等操做。更详细的信息可参考dojo/parser。
The dojo/parser
also supports auto-requiring in modules. This means you don't necessairly have to require in the module before invoking the require. If you set isDebug
to true
though, it will warn you if you are requiring modules this way
The final area to briefly touch on in this tutorial is the Dojo builder. In Dojo 1.7 it was completely rewritten. Partly it was to handle the significant changes with AMD, but it was also designed to modernize it and make it very feature rich. It is a topic too vast for this tutorial. You should read the Creating Builds tutorial for information, but be prepared to forget everything you knew about the old builder in order to embrace the "modern" builder.
最后的部分,咱们讨论下dojo构建器,在dojo1.7时,该部分被重写。一部分变化时为了适应AMD架构,但其新的架构模式让其对外能够提供更加丰富的功能。这是一个很庞大的话题。你能够在Creating Builds教程中看到更多的详细信息。可是你要准备好忘掉一切旧的应用模式,拥抱新的现代的dojo构造器。
但愿你对进入现代dojo世界特别感兴趣,虽然须要您花费一些精力忘记旧的用法,去开始新的dojo之路,但一旦你开始行动,你就很难在回去了,你会发现已经开始使用模块化思想开发您的应用程序。
请记住现代dojo的使用方式:
get()
and a set()
for what you want to do.