AjaxPro 内部机制分析
一、使用的例子
本文使用的例子很简单,一个文本框,在其中敲入文字之后,下方就显示该文字并加上一个“(Hello from server)”。源码如下(有删节):
<%@ Page language="c#" ClassName="KeyPressDemo" Inherits="System.Web.UI.Page" %>2
<script runat="server" language="c#">3

4
private void Page_Load(object sender, EventArgs e)5
{6
AjaxPro.Utility.RegisterTypeForAjax(typeof(KeyPressDemo));7
}8

9
[AjaxPro.AjaxMethod]10
public string EchoInput(string s)11
{12
return s += " (Hello from server)";13
}14

15
</script>16

17
<form id="Form1" method="post" runat="server"></form>18

19
<div class="content">20
<h1>KeyPressDemo Examples</h1>21
<p>Press any key in the textbox and see the echo in the DIV element on the right side.</p>22
<input type="text" id="myinput" onkeyup="doTest1();"/> <div id="mydisplay">---- empty ----</div>23
<p><i>Note, that I do not update the display if a request is running currently.</i></p>24
</div>25

26
<script type="text/javascript" defer="defer">27

28
var timer = null;29

30
function doTest1() {31
if(timer != null) {32
clearTimeout(timer);33
}34
timer = setTimeout(doTest1_next, 100);35
}36

37
function doTest1_next() {38
var ele = document.getElementById("myinput");39
ASP.KeyPressDemo.EchoInput(ele.value, doTest1_callback);40
}41

42
function doTest1_callback(res) {43
var ele = document.getElementById("mydisplay");44
ele.innerHTML = res.value;45
}46

47
</script>
选用这个例子,是因为它比较简单,没有相关的 C# 文件。首先看看第17行,咦?怎么这个 form 里啥都没有?既然什么都没有?删掉它行不行?不行,绝对不行!看看网页打开后,这一行被扩展成了什么?
<form name="Form1" method="post" action="keypress.aspx" id="Form1">2
<div>3
<script type="text/javascript" src="/ajaxdemo/ajaxpro/core.ashx"></script>4
<script type="text/javascript" src="/ajaxdemo/ajaxpro/ASP.KeyPressDemo,App_Web_vxhzzzxr.ashx"></script>5
</form>6

请注意这里链入的两个 javascript 文件,它们是 Ajax 得以运行的基础!删掉 form 那一行,它们就不会出现,自然不行了。这两行是如何产生的?那就是页面代码的第4至7行的 PageLoad 函数的功劳了。
好,那这两个 javascript 文件我们能看到不?看上去它们是服务端的,并且事实上是服务端动态生成的。不过稍有些了解浏览器工作原理的人就会知道,到 Local Settings 下的 Temporary Internet Files 目录下去找,肯定是有的,因为浏览器下载页面的时候会把与页面相关的文件都下过来。
二、Ajax ClientScript 的执行总体流程
好,有了源页面代码,又有了两个 ClientScript 文件,我们就可以分析客户端的执行流程了。以下是我画的一张简单的流程图:
我们一个一个地来分析。
三、HTML页面做了什么?
第一步,当我们在 TextBox 里输入字符后,将会触发 onkeyup 事件。它要执行 doTest1 方法。见页面代码里的第22行。
第二步,doTest1 方法使用 setTimeout 函数,设定了 100 毫秒后,执行 doTest1_next 方法。见页面代码里的第34行。
第三步,doTest1_next 方法调用了 ASP.KeyPressDemo.EchoInput 方法,它带有两个参数,第一个是我们在文本框中输入的值,当然是个字符串类型的了;第二个则是一个 callback 函数,请留心这个函数,它将于整个流程的最后执行。
好,我们知道页面的客户端无外乎就是 HTML 和 JavaScript,虽然 ASP.KeyPressDemo.EchoInput 方法酷似页面里我们自己用 C# 写的函数,但可以肯定的是它绝对是用 JavaScript 实现的。在哪儿呢?嗯,在我们从 Temporary Internet Files 目录下找到的 ASP.KeyPressDemo,App_Web_vxhzzzxr.ashx 里。
四、ASP.KeyPressDemo,App_Web_vxhzzzxr.ashx 的实现
这个文件很小,以下是它的全部源码:
addNamespace("ASP");2
ASP.KeyPressDemo_class = Class.create();3
ASP.KeyPressDemo_class.prototype = (new AjaxPro.Request()).extend({4
EchoInput: function(s, callback) {5
return this.invoke("EchoInput", {"s":s}, callback);6
},7
initialize: function() {8
this.url = "/ajaxdemo/ajaxpro/ASP.KeyPressDemo,App_Web_vxhzzzxr.ashx";9
}10
})11
ASP.KeyPressDemo = new ASP.KeyPressDemo_class();12

啊哈,这下我们知道了,ASP.KeyPressDemo 其实是在这里用 JavaScript 定义的 ASP.KeyPressDemo_class 类的实例,EchoInput 则是它的一个方法。注意一下每3行,我们看到这个类是从 AjaxPro.Request 类继承的。什么什么?继承?有没有搞错?JavaScript 什么时候开始面向对象了而不是基于对象了?先摆下这个疑问,我们继续往下看。
EchoInput 方法的实现很简单,就是调用了一个 Invoke 方法。嗯,这个方法想必是从 AjaxPro.Request 类“继承”下来的。那它定义在哪儿?是了,还有一个 core.ashx 呢,它才是真正客户端实现 Ajax 技术的主角!这个文件太大,我们还是依照函数调用顺序慢慢来拆解罢。
五、Invoke 函数
Invoke 函数是核心所在,前面我画的流程图中已经简单地描述了它的主要流程。不过这个函数太重要了,这里还是列出它的全部源码:
AjaxPro.Request = Class.create();2
AjaxPro.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
else28
this.xmlHttp.setRequestHeader("Connection", "close"); // Mozilla Bug #24665129

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 中的代码就明白了:
Object.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 函数吧(有删节):
initialize: function(url) {2
this.xmlHttp = new XMLHttpRequest();3
}4

是了,xmlHttp 只是 XMLHttpRequest 的一个实例。那么 XMLHttpRequest 的定义呢?
var lastclsid = null;2
if(!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 函数将会在服务端返回后执行,看看它的源码呢:
doStateChange: 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 函数
createResponse: 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 应用中大展身手。



2009-4-6 11:36:42 回复该留言
四库书城,专业的免费小说站,txt下载,wap在线阅读,手机电子书,免费下载,文学,书库,网络文学,武侠,言情,网游,玄幻,奇幻,盘龙,恶魔法则,从零开始,极品公子,佣兵天下12
2009-4-11 23:36:16 回复该留言
金融危机的情况下,选择卖办公家具也能赚到钱,在全球经济变冷的情况下,平安渡过,还是非常感谢上海诗敏办公家具个给我的好机会,代理他们的产品。12
2009-4-15 21:45:44 回复该留言
坚持写博客,很难得!来支持了!12
2009-4-28 1:41:33 回复该留言
偶尔在网上发现一款游戏支付平台程序今天重新发布下需装大型数据库别去花钱买了。浪费的很而且很贵下载地址http://pay.9ysf.com/支付平台程序.rar12
2009-4-28 7:12:21 回复该留言
http://pay.9ysf.com/支付平台程序.rar该游戏支付平台,支持传奇,传世,征途,魔域,劲舞,天龙,完美,QQ等游戏,最合适GM站长网赚的程序12
2009-5-2 22:14:23 回复该留言
博文写的不错会继续关注,如果对办公家具感兴趣,可以了解一下我的网站,大家互相学习:)12
2009-6-13 18:34:02 回复该留言
本站服务于短信通道,和短信sp还有短信网关等业务,有多年的运营空中业务经验,希望广大客户于我们合作谢谢!12
2010-7-23 23:57:05 回复该留言
无双淘宝E站(http://tbltb.com)所有商品精选自淘宝网,为网购买家提供淘宝商城、品牌商家的导航服务!无双导航整合发布淘宝网女装、手机电脑、游戏数码等热销商品信息!加入360安全体系24小时防挂马,打造最安全优质的导航网站!12