内容


使用 Dojo 的 Ajax 应用开发进阶教程,第 6 部分

Dojo 基本库深入介绍

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 使用 Dojo 的 Ajax 应用开发进阶教程,第 6 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:使用 Dojo 的 Ajax 应用开发进阶教程,第 6 部分

敬请期待该系列的后续内容。

Dojo 基本库(Dojo Base)是 Dojo 框架的核心,包含了与 Ajax 应用开发相关的核心内容,也是 Dojo 核心库(Dojo Core)、Dojo 用户界面库(Dijit)和 Dojo 扩展库(Dojox)的基础。Dojo 基本库中包含的内容比较多,下面将详细的对其中的各个模块进行介绍。与 DOM 查询和操作以及事件处理相关的内容已经在本系列中的其它文章中进行介绍,此处不再赘述。而其中与面向对象相关的内容将在其它文章中进行介绍。下面首先介绍 Dojo 基本库中包含的辅助工具方法。

辅助工具方法

在 Ajax 应用开发中,经常会需要编写一些工具方法来辅助开发,以减少代码重复。Dojo 基本库中已经为一些开发中经常会用到的功能提供了辅助方法。使用这些方法提高开发效率和代码质量。

数组处理

数组处理是 Ajax 应用开发中的常见操作。Dojo 基本库提供了一些方法用来方便的对数组进行处理,完成一些典型的任务。这些与数组处理相关的方法的具体说明如下所示:

  • dojo.forEach(array, callback, scope):该方法用来遍历数组并对其中的每个元素执行操作。其参数 array表示的是数组,callback表示的是对每个元素所执行操作的 JavaScript 方法,可选的 scope表示的是 callback方法调用时 this所指向的对象。callback方法在被调用时会传入三个参数,分别表示当前元素、当前元素在数组中的序号以及数组本身。
  • dojo.every(array, callback, scope):该方法用来判断是否数组的全部元素都满足特定的条件。其三个参数的含义与 dojo.forEach()方法相同。callback方法通过返回真假值来声明某个元素是否符合条件。
  • dojo.some(array, callback, scope):该方法用来判断数组中是否至少有一个元素满足特定的条件。其三个参数和 callback方法的含义与 dojo.every()相同。
  • dojo.map(array, callback, scope):该方法用来对数组中的每个元素执行操作,并返回一个数组包含操作的结果。其三个参数的含义与 dojo.forEach()相同。
  • dojo.filter(array, callback, scope):该方法用来对数组中包含的元素进行过滤,只保留满足特定条件的元素。其三个参数的含义与 dojo.forEach()相同。callback方法通过返回真假值来声明某个元素是否应该被保留。
  • dojo.indexOf(array, value, fromIndex, findLast):该方法用来在数组中查找指定的元素,如果找到就返回该元素在数组中的序号,否则返回 -1。其参数 array表示数组,value表示要查找的元素值,fromIndex表示查找的起始序号位置,findLast表示是否从数组末尾开始查找。

代码清单 1中给出了上述方法的使用示例。

清单 1. Dojo 基本库数组处理方式示例
 var array = [2, 4, 6, 8, 10]; 
 dojo.forEach(array, function(number, i) { 
    alert("第" + (i + 1) + "个数是:" + number); 
 }); 
 var allEven = dojo.every(array, function(number) { 
    return number % 2 == 0; 
 }); 
 if (allEven) { 
    alert("数组中全部是偶数。"); 
 } 
 var hasTrimerous = dojo.some(array, function(number) { 
    return number % 3 == 0; 
 }); 
 if (hasTrimerous) { 
    alert("数组中包含 3 的倍数。"); 
 } 
 var tenTimes = dojo.map(array, function(number) { 
    return number * 10; 
 }); 
 alert("数组中所有元素乘以 10:" + tenTimes.join(",")); 
 var lessThanFive = dojo.filter(array, function(number) { 
    return number < 5; 
 }); 
 alert("数组中小于 5 的元素:" + lessThanFive.join(",")); 
 alert("数组中 6 的序号是:" + dojo.indexOf(array, 6));

在介绍完 Dojo 基本库中对数组的处理方式之后,下面介绍对 JavaScript 方法的处理。

JavaScript 方法处理

JavaScript 方法被调用的时候,关键词 this所指向的对象与该方法被调用的方式有关。开发人员容易错误的理解 this所指向的对象而造成问题。Dojo 基本库的 dojo.hitch(scope, method)方法返回一个新的 JavaScript 方法。该方法的作用与参数 method所表示的方法相同,不过它保证被调用时 this关键词始终指向的是参数 scope所表示的对象。除了 scopemethod两个参数之外,使用 dojo.hitch()的时候还可以添加任意多的其它参数。这些参数被作为 method方法调用时的参数来使用。这样相当于创建了一个新的方法,并且预先设置了原来方法中一些参数的值。另外一个方法 dojo.partial(method)的作用与 dojo.hitch()类似,只是少了参数 scope,因此不会设置调用时 this关键词所指向的对象。代码清单 2中给出了 dojo.hitch()的使用示例。

清单 2. dojo.hitch() 使用示例
 var obj = { 
    name : "Alex", 
    lang : "中文"
 }; 
 var func = dojo.hitch(obj, function() { 
    alert(this.name); 
 }); 
 func(); 
    
 function sayHello(to, message) { 
    alert(to + ":" + message + "(" + this.lang + ")"); 
 } 
 var sayHelloToAlex = dojo.hitch(obj, sayHello, "Alex"); 
 sayHelloToAlex("你好!");

代码清单 2中,通过调用 dojo.hitch()得到的 func方法在被调用的时候,其 this关键词指向的对象是 obj,因此 this.name的值是“Alex”。第二次调用 dojo.hitch()的时候传入了参数“Alex”,把它作为 sayHello方法的第一个参数 to的固定值。这样得到的 sayHelloToAlex方法就只需要传入一个参数 message的值即可。

在介绍完 dojo.hitch()之后,下面介绍与字符串处理相关的内容。

字符串处理

下面介绍 Dojo 基本库中与字符串处理相关的两个方法:dojo.trim()dojo.replace()

dojo.trim(str)用来去除字符串首尾的白字符。如 dojo.trim(" Hello World! ")的结果是 "Hello World!",原字符串中首尾的空格被去掉。

dojo.replace(template, map, pattern)提供了一种简单的字符串模板实现,适合用来生成 HTML 片段和显示给最终用户的消息。参数 template表示的是字符串模板,其中包含一些占位符。在运行时刻,这些占位符被替换成真实的值。参数 map表示的是一个 JavaScript 对象或是方法。如果是 JavaScript 对象的话,其与占位符名称相同的属性的值就是真实值;如果是 JavaScript 方法的话,该方法的调用结果作为占位符的真实值。参数 pattern表示的是占位符的正则表达式模式,默认情况下,占位符是通过 {}来表示的。代码清单 3中给出了 dojo.replace()的使用示例。

清单 3. dojo.replace() 使用示例
 var data = { 
    name : "Alex", 
    address : { 
        country : "中国", 
        city : "北京"
    } 
 }; 
 var template = "{name} : {address.country} - {address.city}"; 
 alert(dojo.replace(template, data)); 
 var template2 = "随机数 :{number}"; 
 alert(dojo.replace(template2, function(_, key) { 
    if (key == "number") { 
        return Math.random(); 
    } 
 }));

代码清单 3中给出了 dojo.replace()的两种基本用法。第一种是传入 JavaScript 对象作为模板中占位符真实值的来源。在表示属性的时候,支持用“.”分隔的嵌套表达式。第二种是使用 JavaScript 方法来动态生成真实值。

在介绍完与字符串处理相关的方法之后,下面介绍其它的辅助工具方法。

其它方法

Dojo 基本库提供了一些方法用来做类型检查:dojo.isString()dojo.isArray()dojo.isFunction()dojo.isObject()分别用来判断是否为字符串、数组、方法和对象。dojo.isArrayLike()用来判断是否类似数组。如果一个对象不是字符串和 JavaScript 方法,同时又包含属性 length,则认为其类似一个数组,dojo.isArrayLike()返回的结果为 truedojo.isAlien()用来判断是否为 JavaScript 内置的方法,即由浏览器提供原生实现的方法。

在 Ajax 应用中使用 JSON 是非常普遍的。Dojo 基本库提供了 dojo.toJson()dojo.fromJson()用来进行 JSON 字符串和 JSON 对象之间的相互转换。dojo.toJson(obj, prettyPrint)把 JSON 对象转换成 JSON 字符串,其参数 obj表示 JavaScript 对象,prettyPrint表示是否使用代码缩进使得输出更加美观。默认的缩进字符是制表符,可以通过 dojo.toJsonIndentStr属性的值进行修改。dojo.fromJson(json)把 JSON 字符串转换成 JSON 对象。在 Ajax 应用中的常见用法是从服务器端获取 JSON 字符串,再通过 dojo.fromJson()变成 JSON 对象来使用。

在介绍完 Dojo 基本库提供的辅助工具方法之后,下面介绍与异步操作相关的 dojo.Deferred

dojo.Deferred

在 Ajax 应用中经常需要执行异步操作。比较典型的场景是通过 XMLHTTPRequest 来异步的从服务器端获取数据。当数据返回之后,再对其进行处理。异步操作对于提升 Ajax 应用的用户体验来说是必不可少的。Dojo 基本库提供了 dojo.Deferred来对异步操作进行抽象。如果一个方法是异步执行的,就可以用一个 dojo.Deferred作为其返回值。对于异步操作,典型的做法是为它添加回调方法。当异步操作完成的时候,回调方法会被调用来执行特定的处理逻辑。dojo.Deferred对象提供了 addCallback()addErrback()addBoth()用来添加异步操作的回调方法:其中 addCallback()用来添加异步操作成功完成时的回调方法,addErrback()添加异步操作出现错误时的回调方法,而 addBoth()添加的回调方法不论异步操作成功还是出现错误,都会被调用。这三个方法的参数的含义与 dojo.hitch()的相同。可以通过这三个方法来添加任意多的回调方法。回调方法按照其被添加的顺序依次串联起来,形成一个链条。当异步操作完成之后,这些回调方法会被依次调用。异步操作的执行结果也会在此链条上进行传递。

对于异步操作的实际执行者来说,当异步操作完成的时候,不论是成功还是出现错误,都需要通知调用者,使得对应的回调方法被调用。dojo.Deferred对象的方法 callback()errback()分别用来通知调用者异步操作成功完成或出现错误。调用的时候可以传入一个参数作为操作的结果或是表示出现的错误的 JavaScript Error对象。。

下面介绍 dojo.Deferred的基本用法。执行异步操作的方法创建并返回一个 dojo.Deferred对象。该方法等待异步操作的完成,并调用此 dojo.Deferred对象的 callback()errback()方法。调用者使用该 dojo.Deferred对象的 addCallback()addErrback()来添加回调方法。代码清单 4中给出了一个示例。

清单 4. dojo.Deferred 使用示例
 function asyncOperation() { 
    var d = new dojo.Deferred(); 
    window.setTimeout(function() { 
        var number = Math.random(); 
        if (number > 0.5) { 
            d.callback(number); 
        } 
        else { 
            d.errback(new Error("不正确的随机数!")); 
        } 
    }, 1000); 
    return d; 
 } 
 var d = asyncOperation(); 
 d.addCallback(function(result) { 
    alert("异步操作完成!结果是:" + result); 
    return result * 10; 
 }); 
 d.addCallback(function(result) { 
    alert("异步操作完成!结果的 10 倍是:" + result); 
    return result; 
 }); 
 d.addErrback(function() { 
    alert("异步操作出现错误!"); 
 });

代码清单 4所示,asyncOperation方法通过 window.setTimeout()来模拟了一个异步操作,在等待 1 秒钟之后,该异步操作会根据随机数的值来确定此次调用是成功还是失败。如果成功,则通过 callback()把此随机数的值作为此操作的结果;如果失败则通过 errback()返回一个自定义的 Error对象。异步操作的调用者通过回调方法来对不同的结果进行处理。这里需要注意的一点是,通过 addCallback()添加的回调方法,需要提供一个返回值。该返回值会被作为下一个回调方法的输入参数。一般的做法是把异步操作的结果直接返回,这样其它回调方法都能得到相同的结果。在 代码清单 4中,为了演示操作结果在回调方法链上的传递性,在上一个回调方法中,返回的是得到的随机数的 10 倍,在下一个回调方法中就得到了上一个回调方法更新之后的结果。在很多情况下,开发人员通常会忽略这一点,在回调方法中不返回任何结果。在只有一个回调方法的情况下,这不会造成问题。不过如果存在多个回调方法,会造成难以调试的问题。

在有些情况下,可能会需要取消一个正在进行中的异步操作,如中止一个正在进行中的 XMLHTTPRequest 请求。dojo.Deferred对象的 cancel()方法用来取消异步操作。在创建 dojo.Deferred对象的时候,可以传入一个参数 canceller。当 cancel()方法被调用的时候,canceller方法会被调用。当一个异步操作被取消的时候,接受错误的回调方法会被调用。

在一个异步操作的执行过程中,也可能执行其它的异步操作。这些操作也可以返回 dojo.Deferred作为结果。多个 dojo.Deferred对象可以通过这种方式嵌套起来,形成复杂的级联结构。

异步操作与同步操作的一个显著不同是编程方式上的。同步操作的结果可以直接使用,而异步操作的结果则需要传入接受结果的回调方法。有些情况下,一个操作可能是同步执行,也可能是异步执行的。这种编程方式的不同,会使得代码编写起来变得复杂。通过 dojo.Deferred可以对这两种操作进行统一的抽象,使得代码变得简单。代码清单 5中给出了一个使用 dojo.Deferred来统一异步和同步操作的示例。

清单 5. 使用 dojo.Deferred来统一异步和同步操作示例
 var cache = {}; 

 function getData(name) { 
    var d = new dojo.Deferred(); 
    if (typeof cache[name] != "undefined") { 
        d.callback(cache[name]); 
    } 
    else { 
        dojo.xhrGet({ 
            url : "/data?name=" + encodeURIComponent(name), 
            load : function(data) { 
                cache[name] = data; 
                d.callback(data); 
            }, 
            error : function(e) { 
                d.errback(e); 
            } 
        }); 
    } 
    return d; 
 } 

 var d = getData("Alex"); 
 d.addCallback(function(data) { 
    // 使用数据
 });

代码清单 5所示,获取数据的操作 getData()可能从本地的缓存中获取,也可能从服务器端获取。前者是同步操作,后者是异步操作。通过 dojo.Deferred的使用,这两种操作的不同被屏蔽起来。

在介绍完 dojo.Deferred之后,下面介绍使用 Dojo 基本库进行主题发布和订阅。

主题发布与订阅

Dojo 中的主题发布与订阅机制可以认为是一个全局的事件总线。每个主题用一个字符串来标识。对此主题感兴趣的组件可以订阅此主题。该主题的发布者会在需要的时候把与主题相关的数据发布出来。所有此主题的订阅者会收到通知,相应的逻辑会被自动调用。

订阅主题是通过 dojo.subscribe(topic, context, method)方法来完成的。它的参数 topic表示主题的名称,context表示主题处理逻辑的 JavaScript 方法调用时 this关键词所指向的对象,method表示主题处理逻辑的 JavaScript 方法。dojo.subscribe()的返回值是此次订阅的标识符,可以将该标识符传入 dojo.unsubscribe()来取消订阅。发布主题通知是通过 dojo.publish(topic, args)来实现的。它的参数 topic是主题的名称,args是与主题相关的数据,类型是一个数组。代码清单 6中给出了使用 Dojo 主题发布与订阅的示例。

清单 6. Dojo 主题发布与订阅示例
 dojo.subscribe("/com/example/Test", function(message) { 
    alert(message); 
 }); 

 dojo.publish("/com/example/Test", ["Hello, Alex."]);

Dojo 的主题发布与订阅机制使用起来简单方便,而且适用范围很广。但是在使用的时候要注意下面几点:

  • 不要用它来取代正常的组件之间的消息传递机制。由于主题发布与订阅在当前页面全局范围内起作用,一种常见的编程模式是使用它替代正常的消息传递机制。比如组件 A 和 B 需要协同工作来完成某项任务,就在组件 B 中通过 dojo.subscribe()来订阅某个主题,而在组件 A 中通过 dojo.publish()来通知组件 B,并传递一些数据。这样的话,组件 A 和 B 之间的协作关系就只是通过一个字符串来表达。如果在应用中大量使用这种模式,会使得不同组件之间的协作关系变得难以维护。
  • 组件从页面中删除的时候,通过 dojo.unsubscribe()来取消其订阅的所有主题。这样可以避免内存泄露和提高性能。
  • 使用包含名称空间的主题名称,如 /com/example/package。这样可以避免主题名称之间的冲突。Dojo 内部的主题名称一般以 /dojo开头,在自己的应用中的主题名称尽量不要以 /dojo作为前缀。

dojo.connectPublisher(topic, object, event)dojo.connect()dojo.publish()的结合体。当特定的事件发生的时候,会有相关的主题被自动发布出来。该方法的参数 topic表示发布出来的主题名称,object表示事件发生的对象,event表示产生的事件。如 dojo.connectPublisher("/com/example/MyTopic", node, "onclick")表示当节点 node被点击的时候,自动发布主题 /com/example/MyTopic

在介绍完主题发布与订阅之后,下面介绍 XMLHTTPRequest 相关的内容。

XMLHTTPRequest

Ajax 应用开发中自然少不了通过 XMLHTTPRequest 从服务器端异步的获取数据。Dojo 基本库中提供了一些与 XMLHTTPRequest 相关的方法,为开发人员屏蔽浏览器兼容性的细节。一般来说,服务器端提供的是 REST 接口,浏览器可以通过 HTTP GET、POST、PUT 和 DELETE 请求与服务器端通信。Dojo 提供了对这四种 HTTP 方法的支持。首先介绍如何发送 HTTP GET 请求。

dojo.xhrGet

在 Ajax 应用中,HTTP GET 请求是最常见的,用来从服务器端获取数据。dojo.xhrGet(args)使用 XMLHTTPRequest 来发出 HTTP GET 请求,其参数 args是一个 JavaScript 对象,其中包含了与此次请求相关的信息。该 JavaScript 对象中可以包含的属性比较多,具体如 表 1所示。

表 1. dojo.xhrGet 可配置的属性列表
属性说明
urlHTTP GET 请求访问的 URL 地址。
handleAs表示如何对服务器端返回的数据进行处理。从服务器端返回的一般是文本数据,Dojo 可以对这些文本数据进行一定的处理。Dojo 提供了一些处理方式,如 textjsonjavascriptxml等,分别表示不做任何处理、通过 dojo.fromJson()转换成 JSON 对象、执行 JavaScript 代码和从返回的文本中创建 XML 文档 DOM 对象。该属性的默认值是 text。开发人员可以根据需要选择合适的处理方式。从 Dojo 1.4 开始,开发人员也可以扩展 Dojo 来添加自己的处理方式。
sync表示发出的是同步请求还是异步请求。
preventCache当设置此属性的值为 true的时候,Dojo 会在 GET 请求的 URL 后面加上一个惟一的查询参数,使得每次请求的 URL 都不一样。由于浏览器是通过 URL 来缓存请求的结果的,这样就绕过了浏览器的缓存机制,使得每次请求都是直接从服务器端获取数据。
content表示一个 JavaScript 对象,其中包含的属性和值会出现在 URL 的查询参数中。
headers表示一个 JavaScript 对象,其中包含 HTTP 请求的头信息。
timeout表示请求的超时时间,以毫秒为单位。对于同步请求不适用。
user/password如果服务器端使用的是 HTTP 基本认证方式的话,用这两个属性值来传递用户名和密码。
load/error/handle这三个表示的是 HTTP GET 请求完成之后的回调方法。它们分别表示请求成功完成、出现错误和总是被调用的回调方法。

dojo.xhrGet()方法返回的是 dojo.Deferred对象,因此除了通过 load/error/handle属性来设置回调方法之外,还可以通过 dojo.Deferred提供的方法来添加回调方法。回调方法被调用时,第一个参数是处理之后的返回结果,第二个参数包含了与请求响应相关的信息,包括 XMLHTTPRequest 对象以及发送请求时传入的参数等。

dojo.xhrPost 和 dojo.rawXhrPost

dojo.xhrPost(args)dojo.rawXhrPost(args)方法用来通过 HTTP POST 请求向服务器端发送数据。这两个方法的参数 args除了可以包含 表 1中给出的属性之外,还可以包含 表 2中给出的属性。

表 2. dojo.xhrPost 可配置的额外属性
属性说明
form当需要提交一个 HTML 表单的时候,设置此属性的值为表单 DOM 节点对象或是 ID。
postData表示想要发送到服务器端的任意数据。

dojo.xhrPost()对服务器端返回结果的处理方式与 dojo.xhrGet()相似。这两个方法的返回结果都是 dojo.Deferred对象。

dojo.xhrPut、dojo.rawXhrPut 和 dojo.xhrDelete

dojo.xhrPut()dojo.rawXhrPut()用来发送 HTTP PUT 请求给服务器端。其用法与 dojo.xhrPost()dojo.rawXhrPost()相似。在发送请求的时候,可以通过属性 putData来设置发送给服务器端的内容。

dojo.xhrDelete()用来发送 HTTP DELETE 请求给服务器端。这个方法用得较少。不过如果服务器端采用 REST 架构的话,这是必不可少的一种方式。

这三个方法返回的都是 dojo.Deferred对象。

上面介绍了如何发送 HTTP GET、POST、PUT 和 DELETE 请求,下面说明如何对请求返回的结果进行处理。

扩展 Dojo 对 XMLHTTPRequest 请求返回结果的处理

从 Dojo 1.4 开始,开发人员可以扩展 Dojo 对 XMLHTTPRequest 请求返回结果的处理。如果 Ajax 应用的服务器端和浏览器代码之间在数据格式上有着某种固定的协议的话,可以把这部分处理的逻辑作为对 Dojo 的扩展。代码清单 7中给出了一个示例。

清单 7. 扩展 Dojo 对 XMLHTTPRequest 请求返回结果的处理
 var templates = { 
        "simple" : "<a href='mailto:{email}'>{name}</a>", 
        "nolink" : "<span>{name}</span>:<span>{email}</span>"
    }; 

 dojo.mixin(dojo.contentHandlers, { 
    "myTemplate" : function(xhr) { 
        var result = dojo.fromJson(xhr.responseText); 
        var templateId = result.templateId; 
        var data = result.data; 
        return dojo.replace(templates[templateId], data); 
    } 
 }); 
    
 dojo.xhrGet({ 
    url : "test.json", 
    handleAs : "myTemplate", 
    load : function(data) { 
        alert(data); 
    }, 
    error : function(e) { 
        alert("出现错误。"); 
    } 
 });

代码清单 7所示,通过为 dojo.contentHandlers对象添加额外的属性,就可以扩展 Dojo 对 I/O 请求结果的内容处理逻辑。这里添加了一个名为 myTemplate的处理方式,它会从返回的 JSON 对象中提取出模板的 ID,再利用 dojo.replace()根据返回的 JSON 对象生成出 HTML 片段。在 dojo.xhrGet()的参数中指定属性 handleAs的值为 myTemplate,Dojo 会自动调用对应的处理逻辑。

I/O 事件通知

从 Dojo 1.4 开始,开发人员可以通过 Dojo 的 主题发布和订阅机制来监测与 I/O 相关的事件,即可以通过 dojo.subscribe()来监听与 I/O 事件相关的特定主题。这些主题的详细信息如 表 3所示。

表 3. I/O 事件相关的主题
主题说明参数
/dojo/io/start如果当前没有其它正在进行中的 I/O 请求,并且新的 I/O 请求被发出的时候,发布此主题。
/dojo/io/send当发出新的 I/O 请求的时候,发布此主题。第一个参数 dfd表示此次请求返回的 dojo.Deferred对象。
/dojo/io/load当 I/O 请求成功完成的时候,发布此主题。第一个参数 dfd表示此次请求返回的 dojo.Deferred对象。第二个参数 response表示返回的结果对象。
/dojo/io/error当 I/O 请求出现错误时,发布此主题。/dojo/io/load相同。
/dojo/io/done当 I/O 请求完成的时候,不论成功还是出现错误,发布此主题。/dojo/io/load相同。
/dojo/io/stop当所有 I/O 请求都完成的时候,发布此主题。

默认情况下,I/O 事件的主题发布功能是禁用的。可以通过设置全局的 Dojo 配置项 djConfig.ioPublish = true来启用它。

I/O 事件通知的一个好处是可以为 Ajax 应用中的 I/O 请求添加全局的处理逻辑。比如应用可能希望在有 I/O 请求发出的时候,显示给用户一些提示信息;而在请求完成的时候,去掉这些提示信息。代码清单 8中给出了一个示例。

清单 8. 使用 I/O 事件主题通知的示例
 var messageNode = dojo.byId("message"); 

 dojo.subscribe("/dojo/io/start", function() { 
    dojo.style(messageNode, "display", "block"); 
 }); 

 dojo.subscribe("/dojo/io/stop", function() { 
    dojo.style(messageNode, "display", "none"); 
 });

代码清单 8,当有 I/O 请求发出的时候,显示消息给用户;当 I/O 请求完成的时候,隐藏此消息。

辅助方法

在发送 I/O 请求的时候可能会需要获取当前页面中的表单数据,或者构造 URL 的查询参数字符串,Dojo 基本库也提供了一些辅助方法来帮助满足这些需求。这些方法的具体说明见 表 4

表 4. 与 XMLHTTPRequest 相关的辅助方法
方法说明
dojo.fieldToObject(inputNode)返回表单中字段的值。
dojo.formToObject(formNode)返回包含表单中所有字段值的 JavaScript 对象。
dojo.objectToQuery(map)把一个 JavaScript 对象转换成 URL 查询参数字符串的形式。
dojo.queryToObject(str)解析 URL 查询参数字符串,并转换成一个 JavaScript 对象。
dojo.formToQuery(formNode)把表单中所有字段的值转换成 URL 查询参数字符串的形式。
dojo.formToJson(formNode, prettyPrint)把表单中所有字段的值转换成 JSON 字符串。

在介绍完 Dojo 提供的对 XMLHTTPRequest 请求的支持之后,下面介绍 Dojo 的模块化机制。

模块化机制

Dojo 库很庞大,由很多模块组成。为了方便对模块的管理和使用,Dojo 基本库中提供了与模块的声明和使用相关的内容。

Dojo 中的一个模块对应的是文件系统上的一个 JavaScript 文件。这个文件中封装与此模块相关的逻辑。模块可以复杂,也可以比较简单。每个模块的 JavaScript 文件应该是可以通过 URL 进行访问的。每个模块都有一个用来作为标识的名称,在应用范围内应该是惟一的。模块的名称一般采用 com.example.xxx这样的格式。模块的名称会转化成加载其 JavaScript 文件时所用的 URL 路径。模块之间可以互相依赖。在一个模块内部,可以通过 dojo.require()来声明所依赖的模块。Dojo 会负责加载一个模块所依赖的其它模块。从性能的方面考虑,同样的模块不需要重复加载。Dojo 提供了 dojo.provide()用来声明一个模块已经被加载了。一般在模块的 JavaScript 文件中使用 dojo.provide()进行声明。

模块名称与其加载路径的对应关系比较简单。只需要把模块名称中的“.”替换成路径分隔符“/”,然后添加在基础 URL 之后就可以了。默认的基础 URL 是包含 dojo.js的目录所对应的路径。可以通过 dojo.baseUrl属性来获取此路径。如果模块的名称不以 dojo作为前缀,则认为模块的根目录与 dojo.js的父目录平级。假设 dojo.js的路径是 /js/dojo/dojo.js,那么模块 com.example.SampleModule的路径是 /js/com/example/SampleModule.js。Dojo 会尝试通过上面的路径去加载模块的 JavaScript 文件。如果希望改变默认的模块路径,可以通过 dojo.registerModulePath(module, prefix)方法,其中参数 module表示的是模块名称,prefix表示的是模块路径。模块路径可以是绝对的,即以“/”或 http开头;也可以是相对的。相对的模块路径是相对于 dojo.baseUrl来计算的。比如 dojo.baseUrl的值是 /js/lib/dojo,那么通过 dojo.registerModulePath("example", "../../example/")的模块的路径是 /js/example/。如果通过 dojo.require("example.test.Sample")声明所依赖的模块,Dojo 会尝试通过路径 /js/example/test/Sample.js来加载其 JavaScript 文件。

在模块开发中,可能会需要引用外部的资源文件,如图片、CSS 文件等。通过 dojo.moduleUrl(module, url)可以把模块内一个相对于模块的路径转换成可以实际访问的路径。它的参数 module表示的是模块名称,url是相对于模块的路径,其返回结果是一个 dojo._Url对象。如 dojo.moduleUrl("example", "images/logo.jpg")表示的路径是 /js/example/images/logo.jpg

在介绍完 Dojo 的模块化机制之后,下面介绍如何用 Dojo 基本库实现动画效果。

动画效果

在 Ajax 应用中可能会需要使用一些动画效果。适当的使用动画效果,可以帮助引导用户注意力,提升应用的用户体验。比如显示从服务器端获取的最近更新的内容的时候,可以将内容区域的背景色从黄色渐变到白色,以吸引用户注意最新的内容。Dojo 基本库中包含了与动画效果相关的基础内容,以及少量简单的动画效果的实现。更多的动画效果被包含在 dojo.fxdojox.fx两个模块中。

dojo.Animation类是 Dojo 中动画效果实现的基础,其中包含动画效果的定义、控制与相关事件。表 5中给出了 dojo.Animation中的属性或方法的说明。

表 5. dojo.Animation 中的属性或方法的说明
属性或方法说明
duration以毫秒为单位的动画效果的持续时间。
repeat表示动画效果重复播放的次数。
delay以毫秒为单位的动画效果播放之前的延迟时间。
rate以毫秒为单位的动画效果中每帧之间的间隔时间。
easing表示一个 JavaScript 方法用来调整播放进度,在适当的时候加速或减速,以获得更好的用户体验。
play(delay, gotoStart)播放动画效果。其参数 delay表示播放之前的延迟时间,gotoStart表示是否从头开始播放。
pause()暂停播放动画效果。
gotoPercent(percent, andPlay)设置当前动画效果的播放进度。其参数 percent表示进度的百分比,andPlay表示是否立即开始播放。
stop(gotoEnd)停止播放动画效果。其参数 gotoEnd表示是否移动动画效果到末尾。
status()返回当前动画效果的状态信息,可能的值有 pausedplayingstopped,分别表示动画效果已暂停、正在播放和已停止。
beforeBegin在动画效果开始播放之前的回调方法。
onBegin在动画效果开始播放之后的回调方法。
onEnd在动画效果播放完成之后的回调方法。
onPlay在动画效果播放之时的回调方法,即 play()方法被调用。
onPause在动画效果暂停播放时的回调方法。
onStop在动画效果停止播放时的回调方法。
onAnimate在动画效果的每一帧被播放时的回调方法。

在 Ajax 应用中的动画效果一般是针对节点上 CSS 属性的值来进行的,比如以渐进的方式改变节点的颜色、字体大小和位置等。dojo.animateProperty(args)可以用来方便的实现基于 CSS 属性值的动画效果。该方法的返回结果是一个 dojo.Animation对象。它的参数 args是一个 JavaScript 对象,可以使用 表 5中给出的属性。除此之外,属性 properties是一个包含要动态改变的 CSS 属性信息的一个 JavaScript 对象,其中包括 CSS 属性的名称、开始值、结束值和单位等。代码清单 9给出了使用 dojo.animateProperty()的示例。

清单 9. 使用 dojo.animateProperty()的示例
 dojo.animateProperty({ 
    node : "sample", 
    rate :   200, 
    duration : 5000, 
    properties : { 
        width : { 
            start : 100, 
            end : 400 
        }, 
        height : { 
            end : 400 
        }, 
        backgroundColor : { 
            start : "yellow", 
            end : "red"
        } 
    } 
 }).play();

代码清单 9所示,通过 dojo.animateProperty()方法同时对节点的宽度、高度和背景颜色添加了动画效果。其中宽度从 100 像素变化到 400 像素,高度从当前值变化到 400 像素,背景颜色从黄色变化到红色。在定义单个 CSS 属性的动画效果时,startend属性的值如果为空,则使用节点的当前值。这两个属性也接受 JavaScript 方法作为其值,该方法的调用结果作为动画效果中所使用的值。dojo.animateProperty()方法返回的是 dojo.Animation对象,调用该对象的 play()方法就可以播放此动画效果。

dojo.anim(node, properties, duration, easing, onEnd, delay)方法是 dojo.animateProperty()的一个简单包装,包含了与动画效果相关的常见属性作为参数。dojo.anim()创建的动画效果会自动开始播放。dojo.fadeIn(args)dojo.fadeOut(args)是 Dojo 基本库提供的两个动画效果,分别实现淡入和淡出。它们是通过改变节点的 CSS 属性 opacity(不透明度)来实现的。

在介绍完实现动画效果之后,下面介绍对 Dojo 进行配置和作用上下文的含义。

配置与作用上下文

下面介绍 Dojo 基本库中两个比较高级的话题。第一个是如何对 Dojo 库的行为进行配置。

djConfig

通过 djConfig这个全局的 JavaScript 对象可以对 Dojo 的一些行为进行配置。其中的一些配置项,如 baseUrlioPublish已经在之前介绍过了。其它的一些配置项如 表 6所示。

表 6. djConfig 中的配置项
属性说明
isDebug默认值为 false。当设为 true的时候,Dojo 将在运行时输出调试信息。
locale设置 Dojo 使用的区域设置。默认情况下,Dojo 将从浏览器获取当前用户的区域设置信息。
modulePaths设置额外的模块路径。这是一个以模块前缀和路径作为属性名称和值的 JavaScript 对象。其作用等同于 dojo.registerModulePath()方法。
afterOnLoad设置为 true的时候表明 Dojo 的 JavaScript 文件是在页面加载完成之后才被动态加载的。

在介绍了 djConfig中包含的配置项之后,下面介绍 Dojo 中很多方法的作用上下文。

作用上下文

在 Dojo 基本库中的很多方法都是有自己默认的作用上下文的。 比如使用 dojo.query()进行查询的时候,如果不指定范围,则默认在当前文档树的全局进行查询。dojo.hitch()如果不指定 this关键词所指向的对象的话,默认使用全局对象。Dojo 把一些全局的对象保存起来,可以在需要的时候进行切换。dojo.global保存的是全局对象,一般是当前页面的 window对象。dojo.doc保存的是文档对象,一般是当前页面的 document对象。通过 dojo.setContext(globalObject, globalDocument)用来改变 dojo.globaldojo.doc的值。如果不希望直接改变上面两个对象的值,而是简单的在新的作用上下文中调用一些方法的话,可以使用 dojo.withGlobal(globalObject, callback, thisObject, cbArguments)dojo.withDoc(documentObject, callback, thisObject, cbArguments)。这两个方法分别可以在给定的全局对象(globalObject)或文档对象(documentObject)下调用方法 callback。参数 thisObjectcbArguments分别表示方法 callback调用时 this关键词所指向的对象和使用的参数。

在一般情况下,应用不需要显式改变 Dojo 默认的作用上下文。一种可能需要改变作用上下文的情况就是对页面上 iframe 中的内容进行操作。通过把 dojo.globaldojo.doc修改成 iframe 中的 window对象和 document对象,会使得接下来对 iframe 中内容的操作变得简单。

在介绍完 Dojo 的配置和作用上下文相关的内容之后,下面介绍使用 Dojo 提供的对面向对象编程模式的支持。

面向对象 JavaScript

JavaScript 并不是一种面向对象的编程语言。它与典型的面向对象语言有比较多的区别,比如函数是一等对象,对象的属性可以任意修改,没有类的概念而使用原型(prototype)等。JavaScript 语言的这些特点,对于熟悉面向对象语言的开发人员来说,是一个不小的挑战,需要完成一些思维方式上的转变。Dojo 基本库提供了一些面向对象的支持,使得开发人员能够以熟悉的方式来使用 JavaScript。

定义 JavaScript 类

在 Dojo 中定义一个 JavaScript 类是很容易的。dojo.declare(className, superclass, props)可以用来很方便的定义一个 JavaScript 类。该方法的参数 className表示 JavaScript 类的名字,一般使用 com.example.MyClass这样的带名称空间的全名,以避免冲突。也可以使用 null来创建一个匿名 JavaScript 类。参数 superclass表示的是该 JavaScript 类的父类。其值可以是 null表示没有父类;可以是单个对象,表示单继承;也可以是一个对象的数组,表示多继承。参数 props表示该 JavaScript 类中要包含的属性,是一个 JavaScript 对象。通过 dojo.declare()定义出类之后,就可以通过 JavaScript 的 new操作符来创建该类的实例。定义类的时候如果指定了参数 className的值,则该类是全局可访问的。参数 props表示的 JavaScript 对象中的属性是添加到 JavaScript 类的原型(prototype)中的,是被该类的所有实例共享的。如果在 props对象中包含数组的时候要格外注意,因为这一个数组对象是被所有的实例共享的。如果不小心的话,可能会造成难以调试的错误。props对象中的属性 constructor表示的是该类的构造方法。

实现了继承之后,在子类中可能会覆盖父类中的某些方法。在子类的新方法中,可能会需要调用父类中同名方法。通过 dojo.declare()定义的 JavaScript 类中的方法,可以使用 this.inherited()来调用父类中的同名方法。一般的用法是 this.inherited(arguments),这样就把子类方法调用时的参数也传递给了父类方法。通过 isInstanceOf()可以判断对象是否是一个 JavaScript 类的实例。该方法支持多继承情况下的判断。应该优先考虑使用该方法,而不是 JavaScript 语言提供的 instanceof操作符。

代码清单 10中给出了 dojo.declare()的示例。

清单 10. dojo.declare() 示例
 dojo.declare("com.example.Counter", null, { 
    constructor : function() { 
        this.counter = 1; 
    }, 
    getNext : function() { 
        return this.counter++; 
    } 
 }); 
 var c = new com.example.Counter(); 
 alert(c.getNext()); 

 dojo.declare("com.example.SuperCounter", com.example.Counter, { 
    increase : function() { 
        this.counter++; 
    }, 
    decrease : function() { 
        this.counter--; 
    } 
 }); 
 var sc = new com.example.SuperCounter(); 
 sc.decrease(); 
 alert(sc.getNext());

代码清单 10所示,通过 dojo.declare()定义了一个 JavaScript 类 com.example.Counter。该类是一个简单的计数器。通过其方法 getNext()可以获得下一个数字。接着又定义了 com.example.Counter类的一个子类 com.example.SuperCounter。在子类中添加了两个新的方法。子类的实例既可以调用自己定义的方法,也可以调用父类的方法。这种使用方式与一般的面向对象编程语言是类似的。

混入(mixin)

在有些时候,需要用一个 JavaScript 对象来扩展另外一个 JavaScript 对象的功能,即把一个 JavaScript 对象混入到另外一个对象中。dojo.mixin()方法提供了这样的能力。该方法接受多个 JavaScript 对象作为参数,并按照参数从右到左的顺序依次混入。第一个参数表示的对象将包含其它参数对象中的全部属性。对于名称相同的属性,右边参数对象的值将覆盖左边参数对象中对应的值。dojo.mixin()的一个常见用法是处理选项的默认值。应用一般来说会为选项提供默认值,用户则会提供自定义的值。实际使用的值应该是用户提供的值覆盖默认值之后的结果。代码清单 11中给出了 dojo.mixin()的一个示例。

清单 11. dojo.mixin() 示例
 var defaultOptions = { 
    lang : "zh_CN", 
    maxLength : 100 
 }; 
 function getOptions(userOptions) { 
    return dojo.mixin({}, defaultOptions, userOptions); 
 } 
 getOptions({ 
    lang : "en"
 });

代码清单 11中,dojo.mixin()的第一个参数是个新创建的空 JavaScript 对象。这样的好处是返回的结果是一个新创建的对象,不会对已有对象造成无意的修改。

其它方法

除了 dojo.declare()dojo.mixin()之外,Dojo 基本库还提供其它几个方法,具体的介绍如下。

  • dojo.extend(constructor, props):该方法把 props中所有的属性和方法都添加到 constructor的原型(prototype)中。这些属性和方法对 constructor的所有实例都是可见的。
  • dojo.delegate(obj, props):该方法会返回一个新的对象。该对象包含 props中的属性和方法。当在该对象中找不到某个属性的时候,会继续在 obj对象中进行查找。
  • dojo.getObject(name, create, context):该方法用来从 context对象中获取名为 name的属性。name属性支持类似 com.example.abc这样的“.”分隔的形式。如果指定参数 create的值为 true,当属性 name不存在的时候,会创建该属性并设置其值为空对象。不指定 context对象时默认为全局对象 dojo.global
  • dojo.setObject(name, value, context):在对象 context中创建名称为 name,值为 value的属性。属性 name同样支持“.”分隔的形式。不指定 context对象时默认为全局对象 dojo.global
  • dojo.exists(name, obj):判断对象 obj中是否包含属性 name。属性 name同样支持“.”分隔的形式。不指定 obj对象时默认为全局对象 dojo.global

在介绍完与面向对象 JavaScript 相关的内容之后,对 Dojo 基本库的介绍就结束了。

总结

要学习 Dojo 框架以及如何在 Ajax 应用中高效的使用 Dojo,Dojo 基本库是一个重要的基础。Dojo 基本库中包含的内容都非常实用,使用起来也比较简单。本文详细介绍了 Dojo 基本库中所包含的大部分重要内容,包括辅助工具方法、dojo.Deferred、主题发布与订阅、XMLHTTPRequest、模块化机制、动画效果、配置与作用上下文和面向对象 JavaScript。Dojo 基本库的其它内容会在本系列的其它文章中进行介绍。

声明

本人所发表的内容仅为个人观点,不代表 IBM 公司立场、战略和观点。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=521164
ArticleTitle=使用 Dojo 的 Ajax 应用开发进阶教程,第 6 部分: Dojo 基本库深入介绍
publish-date=09132010