• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
专注于技术,专注于生活
这样才是程序员的生命
博客园    首页    新随笔    联系   管理    订阅  订阅

结合实例探讨AjaxPro 内部机制

 

 

应当承认我这人实在算不上弄潮儿,Ajax 早已流行得一塌糊涂,我却始终没有来研究一下这个东东。上次做网站的时候,BOSS 就跟我讲过,可以参考一下 Ajax 的技术,我嘴上答应,心里却不是十分的在乎。究其原因,一来是我这人比较固步自封,二来起初确实也没太相信 Ajax 真有 BOSS 说的那么神奇。
  转变是从昨天天始的,这一周在公司主要精力都是在用 C++ 写 framework,不得不承认它比较辛苦,细枝末节之处非常之烦,昨天下午呆着呆着就不想干活了,就开始四处游荡,正好看到我们自己也有项目已经成功应用了 Ajax,于是也就想看一看,无奈那帮家伙的开发文档是出奇的少,只好在网上找找资料,自己研究研究吧。
  作为一个技术人员,我看到一项新技术,总是喜欢琢磨琢磨它内部是如何实现的。在对 Ajax 有了初步认识以后,自然想看看其内部机制,但是令我失望的是,至少介绍 Ajax 内部实现的文章少之又少,好容易找到一篇,却也只是简单列了列一些 javascript 代码,并且没什么解释,颇为郁闷。想想求人不如求己,况且自己研究的或许印象更深一些。于是找到了一个 AjaxPro,下来琢磨琢磨,只是对于 JavaScript 我实在知之甚少,不明白之处依然很多,不过还是想写出来,抛砖引玉,望高人们不吝指教。
  一、使用的例子
  本文使用的例子很简单,一个文本框,在其中敲入文字之后,下方就显示该文字并加上一个“(Hello from server)”。源码如下(有删节):

[运行代码] [复制到剪贴板] [ ± ]
CODE:
1<%@ Page language="c#" ClassName="KeyPressDemo" Inherits="System.Web.UI.Page" %>
2<script runat="server" language="c#">
3
4private void Page_Load(object sender, EventArgs e)
5{
6    AjaxPro.Utility.RegisterTypeForAjax(typeof(KeyPressDemo));
7}
8
9[AjaxPro.AjaxMethod]
10public 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
28var timer = null;
29
30function doTest1() {
31    if(timer != null) {
32      clearTimeout(timer);
33    }
34    timer = setTimeout(doTest1_next, 100);
35}
36
37function doTest1_next() {
38    var ele = document.getElementById("myinput");
39    ASP.KeyPressDemo.EchoInput(ele.value, doTest1_callback);
40}
41
42function doTest1_callback(res) {
43    var ele = document.getElementById("mydisplay");
44    ele.innerHTML = res.value;
45}
46
47</script>


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

[运行代码] [复制到剪贴板] [ ± ]
CODE:
1<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 的实现
  这个文件很小,以下是它的全部源码:

[运行代码] [复制到剪贴板] [ ± ]
CODE:
1addNamespace("ASP");
2ASP.KeyPressDemo_class = Class.create();
3ASP.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})
11ASP.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 函数是核心所在,前面我画的流程图中已经简单地描述了它的主要流程。不过这个函数太重要了,这里还是列出它的全部源码:

[运行代码] [复制到剪贴板] [ ± ]
CODE:

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 函数后面再介绍。
   六、解释“继承”51aspx
  前面我们好几次看到貌似继承。当然它们都仅仅是貌似而已。看看以下 core.ashx 中的代码就明白了:

[运行代码] [复制到剪贴板] [ ± ]
CODE:
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 函数吧(有删节):

[运行代码] [复制到剪贴板] [ ± ]
CODE:
1initialize: function(url) {
2    this.xmlHttp = new XMLHttpRequest();
3}
4


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

[运行代码] [复制到剪贴板] [ ± ]
CODE:
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 函数将会在服务端返回后执行,看看它的源码呢:

[运行代码] [复制到剪贴板] [ ± ]
CODE:
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 函数

[运行代码] [复制到剪贴板] [ ± ]
CODE:
1createResponse: function() {
2    var r = new Object();
3    r.error = null;
4    r.value = null;
5
6    var responseText = new String(this.xmlHttp.responseText);//51aspx.com
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 的流程就差不多是完成了。51aspx.com
  十、简单总结一下
  呼,长出一口气。总算可以告一段落了,AjaxPro 服务端的拆解过段时间再说吧。
  在分析 ClientScript 端的时候真是大有感触,JavaScript 其实远比人们想象的强大和管用。其实我同大多数人一样,起初也对它很不感冒,但是之前曾有两件事让我改变了观念。其一是阅读了黄忠成的《深入剖析 ASP.NET 组件设计》,才发现原来许多强大炫目的 ASP.NET 的控件,其实都是用的 JavaScript 实现。其二是在研究国外某文档浏览器实现的时候,发现人家使用 JavaScript 在 IE 下相当完美地实现了强大灵活有如桌面程序的界面和功能,真是吃惊不小。当时就发现了自己对 JavaScript 的了解实在是严重汗颜,惭愧无地。无奈平时没有多少时间去学习提高自己,只能偶尔抽抽空余时间了解了解,充充电吧。
posted @ 2008-11-06 17:04  錒飛  阅读(511)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3