Ajax 原理

作为一个技术人员,我看到一项新技术,总是喜欢琢磨琢磨它内部是如何实现的。在对 Ajax 有了初步认识以后,自然想看看其内部机制,但是令我失望的是,至少介绍 Ajax 内部实现的文章少之又少,好容易找到一篇,却也只是简单列了列一些 javascript 代码,并且没什么解释,颇为郁闷。想想求人不如求己,况且自己研究的或许印象更深一些。于是找到了一个 AjaxPro,下来琢磨琢磨,只是对于 JavaScript 我实在知之甚少,不明白之处依然很多,不过还是想写出来,抛砖引玉,望高人们不吝指教。

  一、使用的例子

  本文使用的例子很简单,一个文本框,在其中敲入文字之后,下方就显示该文字并加上一个“(Hello from server)”。源码如下(有删节):

1Ajax 原理<%@Pagelanguage="c#"ClassName="KeyPressDemo"Inherits="System.Web.UI.Page"%>
2Ajax 原理<scriptrunat="server"language="c#">
3Ajax 原理
4Ajax 原理privatevoidPage_Load(objectsender,EventArgse)
5Ajax 原理{
6Ajax 原理AjaxPro.Utility.RegisterTypeForAjax(typeof(KeyPressDemo));
7Ajax 原理}

8Ajax 原理
9Ajax 原理[AjaxPro.AjaxMethod]
10Ajax 原理publicstringEchoInput(strings)
11Ajax 原理{
12Ajax 原理returns+="(Hellofromserver)";
13Ajax 原理}

14Ajax 原理
15Ajax 原理
</script>
16Ajax 原理
17Ajax 原理<formid="Form1"method="post"runat="server"></form>
18Ajax 原理
19Ajax 原理<divclass="content">
20Ajax 原理<h1>KeyPressDemoExamples</h1>
21Ajax 原理<p>PressanykeyinthetextboxandseetheechointheDIVelementontherightside.</p>
22Ajax 原理<inputtype="text"id="myinput"onkeyup="doTest1();"/><divid="mydisplay">----empty----</div>
23Ajax 原理<p><i>Note,thatIdonotupdatethedisplayifarequestisrunningcurrently.</i></p>
24Ajax 原理</div>
25Ajax 原理
26Ajax 原理<scripttype="text/javascript"defer="defer">
27Ajax 原理
28Ajax 原理vartimer=null;
29Ajax 原理
30Ajax 原理functiondoTest1(){
31Ajax 原理if(timer!=null){
32Ajax 原理clearTimeout(timer);
33Ajax 原理}

34Ajax 原理timer=setTimeout(doTest1_next,100);
35Ajax 原理}

36Ajax 原理
37Ajax 原理functiondoTest1_next(){
38Ajax 原理varele=document.getElementById("myinput");
39Ajax 原理ASP.KeyPressDemo.EchoInput(ele.value,doTest1_callback);
40Ajax 原理}

41Ajax 原理
42Ajax 原理functiondoTest1_callback(res){
43Ajax 原理varele=document.getElementById("mydisplay");
44Ajax 原理ele.innerHTML=res.value;
45Ajax 原理}

46Ajax 原理
47Ajax 原理
</script>

  选用这个例子,是因为它比较简单,没有相关的 C# 文件。首先看看第17行,咦?怎么这个 form 里啥都没有?既然什么都没有?删掉它行不行?不行,绝对不行!看看网页打开后,这一行被扩展成了什么?

1Ajax 原理<formname="Form1"method="post"action="keypress.aspx"id="Form1">
2Ajax 原理<div>
3Ajax 原理<scripttype="text/javascript"src="/ajaxdemo/ajaxpro/core.ashx"></script>
4Ajax 原理<scripttype="text/javascript"src="/ajaxdemo/ajaxpro/ASP.KeyPressDemo,App_Web_vxhzzzxr.ashx"></script>
5Ajax 原理</form>
6Ajax 原理

  请注意这里链入的两个 javascript 文件,它们是 Ajax 得以运行的基础!删掉 form 那一行,它们就不会出现,自然不行了。这两行是如何产生的?那就是页面代码的第4至7行的 PageLoad 函数的功劳了。

  好,那这两个 javascript 文件我们能看到不?看上去它们是服务端的,并且事实上是服务端动态生成的。不过稍有些了解浏览器工作原理的人就会知道,到 Local Settings 下的 Temporary Internet Files 目录下去找,肯定是有的,因为浏览器下载页面的时候会把与页面相关的文件都下过来。

  二、Ajax ClientScript 的执行总体流程

  好,有了源页面代码,又有了两个 ClientScript 文件,我们就可以分析客户端的执行流程了。以下是我画的一张简单的流程图:

Ajax 原理

  我们一个一个地来分析。

  三、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 的实现

  这个文件很小,以下是它的全部源码:

1Ajax 原理addNamespace("ASP");
2Ajax 原理ASP.KeyPressDemo_class=Class.create();
3Ajax 原理ASP.KeyPressDemo_class.prototype=(newAjaxPro.Request()).extend({
4Ajax 原理EchoInput:function(s,callback){
5Ajax 原理returnthis.invoke("EchoInput",{"s":s},callback);
6Ajax 原理}
,
7Ajax 原理initialize:function(){
8Ajax 原理this.url="/ajaxdemo/ajaxpro/ASP.KeyPressDemo,App_Web_vxhzzzxr.ashx";
9Ajax 原理}

10Ajax 原理}
)
11Ajax 原理ASP.KeyPressDemo=newASP.KeyPressDemo_class();
12Ajax 原理

  啊哈,这下我们知道了,ASP.KeyPressDemo 其实是在这里用 JavaScript 定义的 ASP.KeyPressDemo_class 类的实例,EchoInput 则是它的一个方法。注意一下每3行,我们看到这个类是从 AjaxPro.Request 类继承的。什么什么?继承?有没有搞错?JavaScript 什么时候开始面向对象了而不是基于对象了?先摆下这个疑问,我们继续往下看。

  EchoInput 方法的实现很简单,就是调用了一个 Invoke 方法。嗯,这个方法想必是从 AjaxPro.Request 类“继承”下来的。那它定义在哪儿?是了,还有一个 core.ashx 呢,它才是真正客户端实现 Ajax 技术的主角!这个文件太大,我们还是依照函数调用顺序慢慢来拆解罢。

  五、Invoke 函数

  Invoke 函数是核心所在,前面我画的流程图中已经简单地描述了它的主要流程。不过这个函数太重要了,这里还是列出它的全部源码:

1Ajax 原理AjaxPro.Request=Class.create();
2Ajax 原理AjaxPro.Request.prototype=(newAjaxPro.Base()).extend({
3Ajax 原理invoke:function(method,data,callback){
4Ajax 原理varasync=typeofcallback=="function"&&callback!=AjaxPro.noOperation;
5Ajax 原理varjson=AjaxPro.toJSON(data)+"/r/n";
6Ajax 原理
7Ajax 原理if(AjaxPro.cryptProvider!=null)
8Ajax 原理json=AjaxPro.cryptProvider.encrypt(json);
9Ajax 原理
10Ajax 原理this.callback=callback;
11Ajax 原理
12Ajax 原理if(async){
13Ajax 原理this.xmlHttp.onreadystatechange=this.doStateChange.bind(this);
14Ajax 原理if(typeofthis.onLoading=="function")this.onLoading(true);
15Ajax 原理}

16Ajax 原理
17Ajax 原理this.xmlHttp.open("POST",this.url,async);
18Ajax 原理this.xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
19Ajax 原理this.xmlHttp.setRequestHeader("Content-Length",json.length);
20Ajax 原理this.xmlHttp.setRequestHeader("Ajax-method",method);
21Ajax 原理
22Ajax 原理if(AjaxPro.token!=null&&AjaxPro.token.length>0)
23Ajax 原理this.xmlHttp.setRequestHeader("Ajax-token",AjaxPro.token);
24Ajax 原理
25Ajax 原理if(MS.Browser.isIE)
26Ajax 原理this.xmlHttp.setRequestHeader("Accept-Encoding","gzip,deflate");
27Ajax 原理else
28Ajax 原理this.xmlHttp.setRequestHeader("Connection","close");//MozillaBug#246651
29Ajax 原理
30Ajax 原理if(this.onTimeout!=null&&typeofthis.onTimeout=="function")
31Ajax 原理this.timeoutTimer=setTimeout(this.timeout.bind(this),this.timeoutPeriod);
32Ajax 原理
33Ajax 原理this.xmlHttp.send(json);
34Ajax 原理
35Ajax 原理json=null;
36Ajax 原理data=null;
37Ajax 原理deletejson;
38Ajax 原理deletedata;
39Ajax 原理
40Ajax 原理if(!async){
41Ajax 原理returnthis.createResponse();
42Ajax 原理}

43Ajax 原理
44Ajax 原理returntrue;
45Ajax 原理}

46Ajax 原理}
);
47Ajax 原理


  嗯,相当复杂啊。我们慢慢地看。

  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 中的代码就明白了:

1Ajax 原理Object.extend=function(destination,source){
2Ajax 原理for(propertyinsource){
3Ajax 原理destination[property]=source[property];
4Ajax 原理}

5Ajax 原理returndestination;
6Ajax 原理}

7Ajax 原理

  哈哈,所谓的“继承”,其实只是个属性拷贝而已。

  七、this.xmlHttp 从何而来?

  前面我们看到了 this.xmlHttp 大显神威。那么它是哪儿来的?看看 AjaxPro.Request 类的 initialize 函数吧(有删节):

1Ajax 原理initialize:function(url){
2Ajax 原理this.xmlHttp=newXMLHttpRequest();
3Ajax 原理}

4Ajax 原理

  是了,xmlHttp 只是 XMLHttpRequest 的一个实例。那么 XMLHttpRequest 的定义呢?

1Ajax 原理varlastclsid=null;
2Ajax 原理if(!window.XMLHttpRequest){
3Ajax 原理
4Ajax 原理functiongetXmlHttp(clsid){
5Ajax 原理varxmlHttp=null;
6Ajax 原理try{
7Ajax 原理xmlHttp=newActiveXObject(clsid);
8Ajax 原理lastclsid=clsid;
9Ajax 原理returnxmlHttp;
10Ajax 原理}
catch(ex){}
11Ajax 原理}

12Ajax 原理
13Ajax 原理window.XMLHttpRequest=function(){
14Ajax 原理if(lastclsid!=null){
15Ajax 原理returngetXmlHttp(lastclsid);
16Ajax 原理}

17Ajax 原理
18Ajax 原理varxmlHttp=null;
19Ajax 原理varclsids=["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"];
20Ajax 原理
21Ajax 原理for(vari=0;i<clsids.length&&xmlHttp==null;i++){
22Ajax 原理xmlHttp=getXmlHttp(clsids[i]);
23Ajax 原理}

24Ajax 原理
25Ajax 原理if(xmlHttp==null){
26Ajax 原理returnnewIFrameXmlHttp();
27Ajax 原理}

28Ajax 原理
29Ajax 原理returnxmlHttp;
30Ajax 原理}

31Ajax 原理}

32Ajax 原理

  哦,原来是在这里真正创建的。说到底还是一个 ActiveXObject 啊。关于这个本文也不再多提。不过代码中还需要注意的一点是,
如果把第19行列出的一大堆clsids 都处理过了还没有得到对象怎么办?注意到第26行 new 了一个 IFrameXmlHttp。

  IFrameHttp 是在 core.ashx 中定义的,它基本上完全模拟了 ActiveXObject 对象的功能。想研究研究的,自己看源码吧。篇幅所限,这里不多讲啦。

  八、doStateChange 函数

  嗯,前面已经提过,异步的话 doStateChange 函数将会在服务端返回后执行,看看它的源码呢:

1Ajax 原理doStateChange:function(){
2Ajax 原理if(this.onStateChanged!=null&&typeofthis.onStateChanged=="function")
3Ajax 原理try{this.onStateChanged(this.xmlHttp.readyState);}catch(e){}
4Ajax 原理
5Ajax 原理if(this.xmlHttp.readyState!=4)
6Ajax 原理return;
7Ajax 原理
8Ajax 原理if(this.xmlHttp.status==200){
9Ajax 原理if(this.timeoutTimer!=null)clearTimeout(this.timeoutTimer);
10Ajax 原理if(typeofthis.onLoading=="function")this.onLoading(false);
11Ajax 原理
12Ajax 原理this.xmlHttp.onreadystatechange=AjaxPro.noOperation;
13Ajax 原理
14Ajax 原理this.callback(this.createResponse());
15Ajax 原理this.callback=null;
16Ajax 原理
17Ajax 原理this.xmlHttp.abort();
18Ajax 原理}

19Ajax 原理}
,
20Ajax 原理

  如果 status 是 200,也就是 OK,那么清除掉超时处理函数,处理 onLoading 事件,最后使用 callback 调用 createResponse 函数。还记得如果不是异步的话,createResponse 将会直接调用而不是通过 doStateChange 吧。

  九、createResponse 函数

1Ajax 原理createResponse:function(){
2Ajax 原理varr=newObject();
3Ajax 原理r.error=null;
4Ajax 原理r.value=null;
5Ajax 原理
6Ajax 原理varresponseText=newString(this.xmlHttp.responseText);
7Ajax 原理
8Ajax 原理if(AjaxPro.cryptProvider!=null&&typeofAjaxPro.cryptProvider=="function")
9Ajax 原理responseText=AjaxPro.cryptProvider.decrypt(responseText);
10Ajax 原理
11Ajax 原理eval("r.value="+responseText+";");
12Ajax 原理
13Ajax 原理if(r.error!=null&&this.onError!=null&&typeofthis.onError=="function")
14Ajax 原理try{this.onError(r.error);}catch(e){}
15Ajax 原理
16Ajax 原理responseText=null;
17Ajax 原理
18Ajax 原理returnr;
19Ajax 原理}

  如果前面的 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 应用中大展身手。

下载请点击:ASP.NET.AJAX深入浅出系列(赵劼).zip