Dojo的XMLHttpRequest函数叫dojo.xhrQ除了把自己取名元W号之外Q这好像是最直接的办法了。它定义在Dojo基本库里Q所以不需要额外的requirep使用。它可以实现M同域内的httph。不q更常用的是dojo.xhrGet和dojo.xhrPostQ它们只不过是对dojo.xhr函数的简单封装;当然Ҏ(gu)REST风格Q还有dojo.xhrPut和dojo.xhrDelete?/p>
q些函数的参数都很统一。除了dojo.xhr的第一个参数是httpҎ(gu)名之外,所有的dojo.xhr*pd函数都接受同一U散列式的参敎ͼ其中包含h的细节,例如url、是否同步、要传给服务器的内容Q可以是普通对象、表单、或者纯文本Q、超时设定、返回结果的cdQ非怸富且可扩展)、以及请求成功和p|时的回调。所有dojo.xhr*函数Q实际上是所有IO函数Q返回g都一P都是一个Deferred对象Q顾名思义Q它能让一些事情gq发生,从而让API用v来更灉|?/p>
下面的两个例子可能会带来一点直观感受:
dojo.xhrGet({
url: "something.html",
load: function(response, ioArgs){
//用responseq一些事
console.log("xhr get success:", response)
return response //必须q回response
},
error: function(response, ioArgs){
console.log("xhr get failed:", response)
return response //必须q回response
}
})
//Deferred对象允许用同步调用的写法写异步调?/span>
var deferredResult = dojo.xhrPost({
url: "something.html",
form: formNode, //Dojo会自动将form转成object
timeout: 3000, //Dojo会保证超时设定的有效?/span>
handleAs: "json" //得到的response被认ؓ是JSONQƈ自动转ؓobject
})
//当响应结果可用时再调用回调函?/span>
deferredResult.then(function(response){
console.log("xhr get success:", response)
return response //必须q回response
})
首先解释一下timeout。除了IE8之外Q目前大多数XMLHttpRequest对象都没有内|的timeout功能Q因此必ȝ setTimeout。当同时存在大量hӞ需要ؓ每一个请求设|单独的定时器,q在某些览器(主要是IEQ会造成严重的性能问题。dojo的做法是只用一个单独的setIntervalQ定时轮询(间隔50msQ所有还未结束的h的状态,q样高效地解决了一切远E请求(包括JSONP?iframeQ的时问题?/p>
值得一提的q有handleAs参数Q通过讄q个参数Q可以自动识别服务器的响应内Ҏ(gu)式ƈ转换成对象或文本{方便用的形式。根据文档,它接受如下|text (默认), json, json-comment-optional, json-comment-filtered, javascript, xml?/p>
而且它还是可扩展的。其实handleAs只是告诉xhr函数去调用哪个格式{换插Ӟ即dojo.contentHandlers对象里的一个方法。例?dojo.contentHandlers.json是处理JSON格式的插件。你可以方便地定制自己所需要的格式转换插gQ当Ӟ你也可修改现有插件的行ؓQ?/p>
dojo.contentHandlers.json = (function(old){
return function(xhr){
var json = old(xhr)
if(json.someSignalFormServer){
doSomthing(json)
delete json.someSignalFormServer
}
return json
}
})(dojo.contentHandlers.json)//一个小技巧,利用传参得到原方?/span>
如果要了解每个参数的l节Q可以参?a style="margin: 0px; padding: 0px; color: #1a64a2; text-decoration: none;">Dojo的文?/a>?/p>
q里特别提一下Dojo在API设计上的两个特点。其一是虚拟的参数cL念:通过利用javascript对象可以灉|扩展的特点,规定一个散列参数属于某个类。例如dojo.xhr*pd函数所接受的参数就UCؓdojo.__XhrArgs。这个类q不存在于实际代码中Q不要试囄 instanceof验证它)Q只停留在概念上Q比抽象c还抽象Q因此给它加上双下划U前~QDojo习惯为抽象类加单下划U前~Q?/p>
q样做看h没什么意思,但实际上化了APIQ因为它使API之间产生了联p,更容易记忆也更易于使用。这一点在对这U类做承时更明显。例?dojo.__XhrArgsl承自dojo.__IoArgsQ这是所有IO函数所必须支持的参数集合,同样l承自dojo.__IoArgs的还?dojo.io.script.__ioArgs和dojo.io.iframe.__ioArgsQ分别用于动态脚本请求和iframeh。子cd向父cL加少量的属性,q样J多的参数就h了树形类l构。原本散列式参数是用_的参数名代替了固定的参数序Q在增加灉|性和可扩展性的同时Q实际上增加了记忆量Q毕竟参数名不能拼错Q,使得API都不像看h那么好用Q有了参数类的设计就~解了这个问题?/p>
q种参数cȝ做法在Dojo里随处可见,L码的话就会发现它们都是被正儿八经C正常代码形式声明在一U特D注释格式里的,像这P
/*=====
dojo.declare("dojo.__XhrArgs", dojo.__IoArgs, {
constructor: function(){
//summary:
//...
//handleAs:
//...
//......
}
})
=====*/
q种格式可以被jsDoc工具自动提取成文档,在文档里q些虚拟出来的类像真的cM样五脏俱全了?/p>
另一个API设计特点是Deferred对象的广泛用。Dojo里的Deferred是基于MochiKit实现E加改进而成的,而后者则是受?python的事仉动网l工具包Twisted里同名概늚启发。概括来说的话,q个对象的作用就是将异步IO中回调函数的声明位置与调用位|分,q样在一个异步IO最l完成的地方Q开发h员可以简单地说货已经CQ想用的可以来拿了,而不用具体地指出到底该调用哪些回调函数。这样做的好处是让异步IO的写法和同步IO一PҎ(gu)据的处理L在取数据函数的外面,而不是里面)Q从而简化异步编E?/p>
具体做法是,异步函数L同步地返回一个代理对象(q就是Deferred对象Q,可以它看做你想要的数据的代表,它提供一些方法以d回调函数Q当数据可用Ӟq些回调函数(可以由很多个)便会按照d序依次执行。如果在取数据过E中出现错误Q就会调用所提供的错误处理函?也可以有很多?Q如果想要取消这个异步请求,也可通过Deferred对象的cancelҎ(gu)完成?/p>
dojo.Deferred的核心方法如下:
then(callback, errback) //d回调函数
callback(result) //表示异步调用成功完成Q触发回调函?/span>
errback(error) //表示异步调用中生错误,触发错误处理函数
cancel() //取消异步调用
Dojoq提供了一个whenҎ(gu)Q同步的值和异步的Deferred对象在用时写法一栗例如:
//某个工具函数的实?/span>
var obj = {
getItem: function(){
if(this.item){
return this.item //q里同步地返回数?/span>
}else{
return dojo.xhrGet({ //q里q回的是Deferred对象
url: "toGetItem.html",
load: dojo.hitch(this, function(response){
this.item = response
return response
})
})
}
}
}
//用户代码
dojo.when(obj.getItem(), function(item){
//无论同步异步Q用工具函数getItem的方式都一?/span>
})
在函数闭包的帮助下,Deferred对象的创建和使用变得更ؓ单,你可以轻易写Z个创建Deferred对象的函敎ͼ以同步的写法做异步的事。例如写一个用store获取数据的函敎ͼ
var store = new dojo.data.QueryReadStore({...})
function getData(start, count){
var d = new dojo.Deferred() //初始化一个Deferred对象
store.fetch({
start: start,
count: count,
onComplete: function(items){
//直接取用上层闭包里的Deferred对象
d.callback(items)
}
})
return d //把它当做l果q回
}
dojo.xhr* 只是XmlHttpRequest对象的封装,׃同源{略限制Q它不能发跨域请求,要跨域还是需要动态创建script标签。Dojo 没有像JQuery一h所有东襉K装在一PJQuery的ajax()Ҏ(gu)可以跨域Q当然用的是JSONPQ所以它不敢把自q为xhrQ,而是坚持一个API只干一件事情。毕竟在大部分应用中Q同域请求比跨域h多得多,如果一个应用不需要跨域,没必要加蝲相关代码。因此与xhr不同Qdojo 的跨域请求组件不在基本库Q而在核心库,需要require一下才能用:
dojo.require("dojo.io.script")
q个包里面基本上只需要用C个函敎ͼdojo.io.script.get()。它也返回Deferred对象Qƈ接受cd?dojo.io.script.__ioArgs的散列参数。受益于虚拟参数c,我们不用从头开始学习这个参敎ͼ它承了dojo.__IoArgsQ因此和dojo.xhr*pd的参数大同小异。唯一需要注意的是handleAs在这里无效了Q代之以jsonp或者checkString?/p>
前者用于实现JSONP协议Q其值由服务器端指定Q当script标签加蝲后就按照JSONP协议执行q个函数Q然后Dojo会自动介入,负责把真正的数据传给load函数。需要指出的是在Dojo1.4以前Q这个参数叫callbackParamNameQ冗长但意义明确。毕竟Dojo太早了,它成型的时候(2005QJSONPq个词才刚出C久。现在callbackParamNameq是可用的(Z向后兼容Q,不过处于deprecated状态?/p>
下面的例子从flickr获取feed数据Q?/p>
dojo.io.script.get({
url: "http://www.flickr.com/services/feeds/photos_public.gne",
jsonp: "jsoncallback", //由flickr指定
content: {format: "json"},
load: function(response){
console.log(response)
return response
},
error: function(response){
console.log(response)
return response
}
})
与jsonp不同QcheckString参数专门用于跨域获取javascript代码Q它其实是那D跨域脚本里的一个有定义的变量的名字QDojo会用它来判断跨域代码是否加蝲完毕Q配合前面提到的timeout机制p实现有效的超时处理?/p>
dojo.io.script.get({
url: "http://......", //某个提供脚本的URL
checkString: "obj",
load: function(response){
//脚本加蝲完毕Q可以直接用其中的对象了,如obj?/span>
Return response
}
})
dojo.io 包里q有一个工具就是iframeQ常用于以不h面的方式上传或下蝲文g。这个很l典的Ajax技巧在Dojo里就是一?dojo.io.iframe.send({...})。这个函数接受dojo.io.iframe.__ioArgsQ相?dojo.__IoArgsQ它只多了一个method参数Q用于指定是用GETq是POSTQ默认)Ҏ(gu)发送请求。下面的例子实C通过无刷新提交表单来上传文gQ?/p>
dojo.io.iframe.send({
form: "formNodeId", //某个form元素包含本地文g路径
handleAs: "html", //服务器将q回html面
load: onSubmitted, //提交成功
error: onSubmitError //提交p|
})
目前send函数的handleAs参数支持html, xml, text, json, 和javascript五种响应格式。除了html和xml之外Q用其他格式有一个比较特别的要求Q就是服务端q回的响应必d有以下格式:
html
head/head
body
textarea真正的响应内?/span>/textarea
/body
/html
q是因ؓ服务器返回的东西是加载在iframe里的Q而只有html面才能在Q何浏览器里保证成功加载(有个DOM在,以后取数据也方便Q。加一个textarea则可以尽量忠实于原始文本数据的格式,而不会受到html的媄响?/p>
如果dojo.xhr*函数以及Deferred机制仍然无法避免代码的乱,那RPC可能是唯一的选择了。dojo.rpc包提供了Z单方法描q语a(SMD)的RPC实现。SMD的原理类gWSDLQWeb服务描述语言Q,不过是基于JSON的,它定义了q程Ҏ(gu)的名U、参数等属性,?Dojo能创建出代理Ҏ(gu)以供调用?/p>
Dojo提供了两U方式实现rpcQXHR和JSONPQ分别对应dojo.rpc.JsonServicecddojo.rpc.JsonpServicec,用户可以Ҏ(gu)是否需要跨域各取所需?/p>
一个简单的例子Q?/p>
var smdObj = {
serviceType: "JSON-RPC",
serviceURL: "http://...."
methods: [
{name: "myFunc", parameters: []}
]
}
var rpc = new dojo.rpc.JsonpService(smdObj) //传入SMD
var result = rpc.myFunc() //直接调用q程Ҏ(gu)Q返回Deferred对象
dojo.when(result, function(result){
//得到l果
})
SMDq没有一个被q泛认可的官Ҏ(gu)准,一直以来都是DojoC领导着它的发展Q以后这个模块也有可能发生改变,但整个RPC基本的框架会保持E_?/p>
Ajaxhq个主题太大Q本文只能挂一漏万Cl一点dojo在这斚w设计和实现的皮毛Q包括基本XHRh、动态script、iframeh、以及RPCQƈ特别了几个有Dojo特色的设计,如timeout机制、虚拟参数类、Deferred对象{?/p>
Dojo由Ajax领域的先׃写就Q相信从它的代码中我们一定能学到更多的东ѝ?/p>
Dojo 作ؓ一个非常实用的 Ajax 实现框架已经被许?web2.0 开发h员广泛用,但?Dojo 会导致客L览器需要加载大量的 Dojo 文gQ导致应用程序性能下降。解?Dojo 性能问题的方法之一是?Dojo 文gq行定制打包和压~?( 提高 Dojo 性能的具体方案请参看“提高Z Dojo ?Web 2.0 应用E序的性能”。Dojo 本n已经提供了一套对 Dojo 库文?( 自己~写?Javascript 文g只要W合 Dojo 的规范同样可以进行打?) q行 build 的工P通过定制库文件和压羃文g的方法来减少览器加载文件的旉?/p>
Dojo 的主要库文g (dojo ?) 大小?1M 左右Qdijit 包和 dojox 包大都?4M-5MQ但我们q不L需要所有的q些库文Ӟ可以Ҏ(gu)开发者自w的需要定制一?Dojo 库,实际使用的库文g大小往往能大大降低;以笔者目前的开发项目ؓ例,l过定制后实际用的 Dojo 库文件大只?300K 左右?/p>
另外 Dojo ?build 工具也通过压羃 Javascript 文g的手D|降低览器加载文件的旉Q该q程中需要?ShrinkSafeQ有兛_详细介绍请参?#8220;http://dojotoolkit.org/docs/shrinksafe”Q,具体的方法如下:
l过压羃处理QJavascript 文g的大M可减?30%-50%Q同时将所有的 Javascript 文g打包成一个文件也减少了浏览器加蝲时多ơ开闭文件的负担Q从而降低了加蝲旉?/p>
jdk 下蝲地址Q?a cmimpressionsent="1">http://java.sun.com/javase/downloads/index.jsp
Dojo 提供?build 工具位于 \dojo\util\buildsrcipts 下,?windows 下调用该目录内的 build.batQlinux 下?build.shQ文件既可执?build 工作?/p>
下面是在一个在 windows 下调?build.bat 的例子:
build profile=base action=release releaseName=myDojo optimize=shrinksafe
该命令中包括了几个最常用的参敎ͼ其意义如下:
实际上在使用 Dojo ?build 工具Ӟ关键在于提供?profile 文g里的内容Q在下面的例子中会详l说?profile 文g的配|方法?/p>
Dojo 库中提供了大量的文g供用者调用,但有的时候我们ƈ不是需要所有的q些文gQ此时我们可以?Dojo ?build 工具定制一份个性化?Dojo 库文Ӟ首先我们需要编写一?profile 文g来描q我们的需求:
/* example.profile.js */ dependencies = { layers: [ // 可以Ҏ(gu)需要制定多个不同的 layer { name: "example.js", // 打包生成?js 文g的名 dependencies: [ // 需要打包的 js 文g列表 "dojo.date", "dojox.uuid" ] } ], prefixes: [ // 讄路径 [ "dijit", "../dijit" ], [ "dojox", "../dojox" ] ] }
注意Q?/em>
该文g (example.profile.js) 攑֜ \dojo\util\buildsrcipts\profiles 目录下,执行Q?/p>
build profile=example action=release releaseName=myDojo optimize=shrinksafe
build 完成后,会在 \dojo 下生成一?release 文g夹,如下图所C:
因ؓ我们讄?build 的参?releaseName=myDojo, 因此 release 下会生成一?myDojo 文g夹,本次 build 产生的文仉|于该文件夹下。在 \dojo\release\myDojo\dojo\ 目录下,我们可以扑ֈ两个文gQexample.js ?example.uncompressed.jsQ这是我们需要的打包后的文gQexample.uncompressed.js 只是包含了我们指定的所?dojo 文gQexample.js 则在 example.uncompressed.js 基础上又q行了压~处理?/p>
对于我们自己~写?Javascript 文gQ我们同样可是借助 Dojo 提供?build 工具q行压羃和打包,前提是这?js 文g需要按?Dojo 相关的的规范~写。打包我们自q Javascript 文g与打?Dojo 文gq没有太大的差别Q假设我们有两个 Javascript 文g如下Q?/p>
/* my.example1 */ dojo.provide("my.example1"); dojo.require("my.example2"); // 声明了对 my.example2 的依? /* * this is a js file witch named example1.js */ /* my.example2 */ dojo.provide("my.example2"); /* * this is a js file witch named example2.js */
?\dojo 下新Z个文件夹“my”, 上面的两个文g攑֜该文件夹下,profile 文g配置如下Q?/p>
/* example.profile.js */ dependencies = { layers: [ { name: "example.js", dependencies: [ "dojo.date", "dojox.uuid", "my.example1" // 注意q里我们只声明了 my.example1 ] } ], prefixes: [ [ "dijit", "../dijit" ], [ "dojox", "../dojox" ], [ "my", "../my"] // 刚才新徏?my 文g多w要在此声明\? ] }
执行Q?/p>
build.bat profile=example action=release releaseName=myDojo optimize=shrinksafe
h意,?profile 文g中,只声明了?my.example1 q行打包Q但?build 生成?example.js 中我们会发现 my.example2 中的内容也已l被dq来了。这是因为在 build q程中,build E序在分?js 文g内容旉过识别一些关键字 ( 例如 dojo.require) 来判断当前文件是否依赖其他的文gQƈ这些依赖的文g一同进行打包。因此当 build E序?my.example1 中读?dojo.require("my.example2"); Ӟ判断文g需要依赖另一个文?my.example2"Q根?prefixes 提供的\?build E序扑ֈ?my.example2.js 文gQƈ该文g的内Ҏ(gu)加进来?/p>
按照上面的例?build 后,我们自己~写?Javascript 文g会和我们定制 Dojo 的文件合q在一个文件中Q我们可能需要独立用这些自q写的 Javascript 文gQ那么我们修改一?profile 文g既可Q?/p>
/* example.profile.js */ dependencies = { layers: [ { // q个 layer 用来打包我们定制?dojo 文g name: "mydojo.js", dependencies: [ "dojo.date", "dojox.uuid" ] }, { // q个 layer 用于打包我们自己?js 文g name: "example.js", dependencies: [ "my.example1" ] } ], prefixes: [ [ "dijit", "../dijit" ], [ "dojox", "../dojox" ], [ "my", "../my"] ] }
执行Q?/p>
build.bat profile=example action=release releaseName=myDojo optimize=shrinksafe
q样 build 后在 \dojo\release\myDojo\dojo\ 我们会分别得?mydojo ?example 两个 layer 的打包文Ӟ
”var identifier”
Q经q压~处理可能就变成?”var _v01”
Q。如果我们的 Javascript 代码中用了 eval
语句Qƈ?eval
的内定w包含了一些我们定义的变量名,׃D打包后的文g出现错误而无法用。例如下面的 Javascript 代码Q?/li>var identifier = “”; eval(“alert(identifier)”);
因ؓl过压羃后变量名 identifier ?build E序以其他的名字替代Q因此在执行 eval Ҏ(gu) , 也就是调?alert(identifier) Ӟ会因为无法识?identifier 而报?undefined 的错误?/p>
本文站在一个初学者的角度单地介绍?Dojo ?build 工具的用方法(关键在于 profile 文g的配|)Q通过以上内容读者应该能够用该工具q行基本的定制和打包处理Q有兴趣的读者可以通过提供的参考资料进行进一步的学习?/p>
Dijit 的类也是一?Dojo c,所?Dijit cȝ声明和定义也是用 dojo.declare 函数Q如清单 10 和清?13 所C。Dijit cL然是 Dojo c,自然也可以承其它类或被其它cLl承。实际上Q一?Dijit cd别于其它 Dojo cL重要的一Ҏ(gu)QDijit c都直接或间接地l承于类 dijit._WidgetQ大部分?Dijit c通过 mixin 的方式承类 dijit._TemplatedQ如清单 13 中的 [dijit._Widget,dijit._Templated]?/p>
让我们回q头来看看清?13Q清?13 中,有一个属性叫 templatePathQ从名字可以看出来Q这个属性指定了 template 文g的\径。除了指?template 文g的\径外Q也可以直接?template 变成一个字W串攑ֈcd义文件中Q这U情况下Q要用到的属性就?templateString 了?/p>
除了 templatePath ?templateString 以外Q还有很多扩展点可以Ҏ(gu)实际需要重载,q些扩展点覆盖了 dijit 的整个生命周期,具体列D如下Q?/p>
constructorQ?/p>
constructor 会在讄参数之前被调用,可以在这里进行一些初始化的工作。Constructor l束后,便会开始设|?Dijit 实例的属性|x dijit 标签中定义的属性Dl?dijit 实例?/p>
postMixInPropertiesQ?/p>
如果你在你的 dijit 中重载这个函敎ͼ它会?dijit 展现之前Qƈ且在 dom 节点生成之前被调用。如果你需要在 dijit 展现之前Q修改实例的属性,可以在这里实现?/p>
buildRenderingQ?/p>
通常情况下这个函C不需要去重蝲Q因?_Templated 为在q里Z做好所有的事情Q包?dom 节点的创建,事情的连接,attach point 的设|。除非你要开发一套完全不一L模板pȝQ否则徏议你不要重蝲q个函数?/p>
postCreateQ?/p>
q个函数会在 dijit 创徏之后Q子 dijit 创徏之前被调用?/p>
startupQ?/p>
当你需要确保所有的?dijit 都被创徏出来了,你可以调用这个函数?/p>
destroyQ?/p>
会在 dijit 被销毁时被调用,你可以在q里q行一些资源回收的工作
先看define。作用是定义一个模块(moduleQ。这个模块可以被require引用Q引用了之后可以用define里面的东ѝ一个模块想当然Q里面干什么事情,不一定全部自己实现。就像h要codingQ除了脑子,也不能没有电(sh)脑、键盘。因此,define的第一个参数就是将要用到的其他模块引进来。第二个参数描述q个模块具体q什么,q且l第一个参C的模块分别v一个朗朗上口的名字。就像下面这D代码描q的样子?/p>
util.js
q是一个工h块,其中一个功能就是把|页上id对应的DOM节点变成U色。当我们要用它的时候,可以用require了?/p>
test.jsp
可以看到Q上面的模块util作ؓ工具模块Q可以在被引用后L调用其功能。这是无状态的Q就好象是一个singleton的对象。但如果我们惛_?#8220;c?#8221;一L东西Q有状态,可以创徏多个对象Q就需要在define里用declare。最典型的例子就是dijit下面的诸多UI控件?/p>
举个很简单的例子Q我希望Zdijit.DialogQ创Z个有Ҏ(gu)功能的dialogQ每ơ打开后能把上面的一Dtext标记为红艌Ӏ?/p>
RedTextDialog.js
RedTextDialog可以重写dijit.Dialog所有的Ҏ(gu)Q也可以自创Ҏ(gu)、变量,实现自己惌的Q意功能。接下来可以用require使用它?/p>
可以看到Q每ơ用RedTextDialogӞ都可以创Z个新的对象实例,因此可以做到互相之间没有关系?/p>
Z在test.jsp中调用上qjs文gQ需要在test.jsp声明js文g的位|?/p>
最后,U定俗成圎ͼ一般definecMutil的singleton模块Qjs文g的名字第一个字母小写;而类似RedTextDialog的类模块Q第一个字母大写?/p>