当前位置 : 主页 > 网页制作 > Dojo >

现代的Dojo(相对于1.6版本)<5>

来源:互联网 收集:自由互联 发布时间:2021-06-15
你可能没在使用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操作的模块摘要信息: Module Description Contains 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 工具包的访问器逻辑已被划分为多个,而不是全用原来的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——它非常强大,功能更加丰富。
网友评论