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

前端之家收集整理的这篇文章主要介绍了现代的Dojo(相对于1.6版本)<5>前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

你可能没在使用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",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!"
  ],on,parser,registry){
      var myClick = function(evt){
          console.log("I was clicked");
      };
 
      parser.parse();
 
      on(dom.byId("button1"),"click",myClick);
      on(registry.byId("button2"),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,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,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:",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"],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,"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",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",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",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,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——它非常强大,功能更加丰富。
原文链接:https://www.f2er.com/dojo/291264.html

猜你在找的Dojo相关文章