ajax|asp.net
五、Invoke 函数
Invoke 函数是核心所在,前面我画的流程图中已经简单地描述了它的主要流程。不过这个函数太重要了,这里还是列出它的全部源码:
1AjaxPro.Request = Class.create();
2AjaxPro.Request.prototype = (new AjaxPro.Base()).extend({
3 invoke: function(method, data, callback) {
4 var async = typeof callback == "function" && callback != AjaxPro.noOperation;
5 var json = AjaxPro.toJSON(data) + "\r\n";
6
7 if(AjaxPro.cryptProvider != null)
8 json = AjaxPro.cryptProvider.encrypt(json);
9
10 this.callback = callback;
11
12 if(async) {
13 this.XMLHttp.onreadystatechange = this.doStateChange.bind(this);
14 if(typeof this.onLoading == "function") this.onLoading(true);
15 }
16
17 this.XMLHttp.open("POST", this.url, async);
18 this.XMLHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
19 this.XMLHttp.setRequestHeader("Content-Length", json.length);
20 this.XMLHttp.setRequestHeader("Ajax-method", method);
21
22 if(AjaxPro.token != null && AjaxPro.token.length > 0)
23 this.XMLHttp.setRequestHeader("Ajax-token", AjaxPro.token);
24
25 if(MS.Browser.isIE)
26 this.XMLHttp.setRequestHeader("Accept-Encoding", "gzip, deflate");
27 else
28 this.XMLHttp.setRequestHeader("Connection", "close"); // Mozilla Bug #246651
29
30 if(this.onTimeout != null && typeof this.onTimeout == "function")
31 this.timeoutTimer = setTimeout(this.timeout.bind(this), this.timeoutPeriod);
32
33 this.XMLHttp.send(json);
34
35 json = null;
36 data = null;
37 delete json;
38 delete data;
39
40 if(!async) {
41 return this.createResponse();
42 }
43
44 return true;
45 }
46});
47
嗯,相当复杂啊。我们慢慢地看。
AjaxPro.Request 类当然不是只有 Invoke 一个函数,这里省去了其它函数。嗯,我们看到,AjaxPro.Request 也是从 AjaxPro.Base “继承”下来的。
第4行的 async,字面上理解就是指异步,这一行什么意思?嗯,如果传进来的 callback 类型是函数,并且不是无操作,那就认为是异步的。
第5行的 json,它可是相当重要啊。这里调用了 AjaxPro.toJSON 方法把传进来的数据进行了某种编码,本例中这个数据当然就是从 doTest1_next 一路传进来的 TextBox 里我们输入的字符串值了,这个函数的实现,本文也不再列出,可以参见 core.ashx 文件。
接下来第7到8行,如果提供了加密,那么就对 json 进行加密。这个好理解。
第12到15行,如果是异步的,那么这里将 doStateChange 函数绑定到 onreadystatechange 事件上去。嗯,这里的绑定其实也是在 core.ashx 文件里声明的一个方法,本文不再阐述它的实现了,大家有兴趣,可以自己去看。绑定完成后,当服务端完成操作后,doStateChange 函数会被调用,这时可以进行更改页面的工作。此外,这里还检测了一下 onLoading 事件。
第17行到第33行可谓核心代码,我们知道 Ajax 就是使用的 XMLHttpRequest 来完成无刷新页面的。这里我们可看到 this.XMLHttp 被用来进行了请求封装。其中值得我们注意的,Content-Length 使用的 json.length,Ajax-method 则使用的就是传进来的 AjaxMethod 方法名称,本例中为 EchoInput。第30、31行设置了超时处理,当然了,页面不能死等嘛。第33行则将 json 发送到服务端。
接下来的第41行,我们看到如果不是异步操作的话,此处将直接调用 createResponse 函数获得响应。那如果是异步操作呢?记得我们设置了 doStateChange 吧?异步的返回处理就是它的事了。createResponse 函数后面再介绍。
六、解释“继承”
前面我们好几次看到貌似继承。当然它们都仅仅是貌似而已。看看以下 core.ashx 中的代码就明白了:
1Object.extend = function(destination, source) {
2 for(property in source) {
3 destination[property] = source[property];
4 }
5 return destination;
6}
7
哈哈,所谓的“继承”,其实只是个属性拷贝而已。
七、this.XMLHttp 从何而来?
前面我们看到了 this.XMLHttp 大显神威。那么它是哪儿来的?看看 AjaxPro.Request 类的 initialize 函数吧(有删节):
1initialize: function(url) {
2 this.XMLHttp = new XMLHttpRequest();
3}
4
是了,XMLHttp 只是 XMLHttpRequest 的一个实例。那么 XMLHttpRequest 的定义呢?
1var lastclsid = null;
2if(!window.XMLHttpRequest) {
3
4 function getXMLHttp(clsid) {
5 var XMLHttp = null;
6 try {
7 XMLHttp = new ActiveXObject(clsid);
8 lastclsid = clsid;
9 return XMLHttp;
10 } catch(ex) {}
11 }
12
13 window.XMLHttpRequest = function() {
14 if(lastclsid != null) {
15 return getXMLHttp(lastclsid);
16 }
17
18 var XMLHttp = null;
19 var clsids = ["MsXML2.XMLHTTP.6.0","MsXML2.XMLHTTP.5.0","MsXML2.XMLHTTP.4.0","MsXML2.XMLHTTP.3.0","MsXML2.XMLHTTP.2.6","Microsoft.XMLHTTP.1.0","Microsoft.XMLHTTP.1","Microsoft.XMLHTTP"];
20
21 for(var i=0; i<clsids.length && XMLHttp == null; i++) {
22 XMLHttp = getXMLHttp(clsids[i]);
23 }
24
25 if(XMLHttp == null) {
26 return new IFrameXMLHttp();
27 }
28
29 return XMLHttp;
30 }
31}
32
哦,原来是在这里真正创建的。说到底还是一个 ActiveXObject 啊。关于这个本文也不再多提。不过代码中还需要注意的一点是,
如果把第19行列出的一大堆clsids 都处理过了还没有得到对象怎么办?注意到第26行 new 了一个 IFrameXMLHttp。
IFrameHttp 是在 core.ashx 中定义的,它基本上完全模拟了 ActiveXObject 对象的功能。想研究研究的,自己看源码吧。篇幅所限,这里不多讲啦。
八、doStateChange 函数
嗯,前面已经提过,异步的话 doStateChange 函数将会在服务端返回后执行,看看它的源码呢:
1doStateChange: function() {
2 if(this.onStateChanged != null && typeof this.onStateChanged == "function")
3 try{ this.onStateChanged(this.XMLHttp.readyState); }catch(e){}
4
5 if(this.XMLHttp.readyState != 4)
6 return;
7
8 if(this.XMLHttp.status == 200) {
9 if(this.timeoutTimer != null) clearTimeout(this.timeoutTimer);
10 if(typeof this.onLoading == "function") this.onLoading(false);
11
12 this.XMLHttp.onreadystatechange = AjaxPro.noOperation;
13
14 this.callback(this.createResponse());
15 this.callback = null;
16
17 this.XMLHttp.abort();
18 }
19},
20
如果 status 是 200,也就是 OK,那么清除掉超时处理函数,处理 onLoading 事件,最后使用 callback 调用 createResponse 函数。还记得如果不是异步的话,createResponse 将会直接调用而不是通过 doStateChange 吧。
九、createResponse 函数
1createResponse: function() {
2 var r = new Object();
3 r.error = null;
4 r.value = null;
5
6 var responseText = new String(this.XMLHttp.responseText);
7
8 if(AjaxPro.cryptProvider != null && typeof AjaxPro.cryptProvider == "function")
9 responseText = AjaxPro.cryptProvider.decrypt(responseText);
10
11 eval("r.value = " + responseText + ";");
12
13 if(r.error != null && this.onError != null && typeof this.onError == "function")
14 try{ this.onError(r.error); }catch(e){}
15
16 responseText = null;
17
18 return r;
19}
如果前面的 json 也就是 Request 是加过密的,这里就需要对 responseText 进行解密。完了之后得到 r.value,r 将会被返回并提供给 callback 函数。本例中将最终传回 doTest1_callback,r 被传入它的 res 参数。最后更新文本框下的字符串,整个 Ajax ClientScript 的流程就差不多是完成了。
十、简单总结一下
呼,长出一口气。总算可以告一段落了,AjaxPro 服务端的拆解过段时间再说吧。
在分析 ClientScript 端的时候真是大有感触,JavaScript 其实远比人们想象的强大和管用。其实我同大多数人一样,起初也对它很不感冒,但是之前曾有两件事让我改变了观念。其一是阅读了黄忠成的《深入剖析 ASP.NET 组件设计》,才发现原来许多强大炫目的 ASP.NET 的控件,其实都是用的 JavaScript 实现。其二是在研究国外某文档浏览器实现的时候,发现人家使用 JavaScript 在 IE 下相当完美地实现了强大灵活有如桌面程序的界面和功能,真是吃惊不小。当时就发现了自己对 JavaScript 的了解实在是严重汗颜,惭愧无地。无奈平时没有多少时间去学习提高自己,只能偶尔抽抽空余时间了解了解,充充电吧。
相信 JavaScript 之类的脚本必将在未来的 Web 应用中大展身手。