|
應(yīng)當(dāng)承認(rèn)我這人實(shí)在算不上弄潮兒,Ajax 早已流行得一塌糊涂,我卻始終沒有來研究一下這個(gè)東東。上次做網(wǎng)站的時(shí)候,BOSS 就跟我講過,可以參考一下 Ajax 的技術(shù),我嘴上答應(yīng),心里卻不是十分的在乎。究其原因,一來是我這人比較固步自封,二來起初確實(shí)也沒太相信 Ajax 真有 BOSS 說的那么神奇。
轉(zhuǎn)變是從昨天天始的,這一周在公司主要精力都是在用 C++ 寫 framework,不得不承認(rèn)它比較辛苦,細(xì)枝末節(jié)之處非常之煩,昨天下午呆著呆著就不想干活了,就開始四處游蕩,正好看到我們自己也有項(xiàng)目已經(jīng)成功應(yīng)用了 Ajax,于是也就想看一看,無奈那幫家伙的開發(fā)文檔是出奇的少,只好在網(wǎng)上找找資料,自己研究研究吧。 作為一個(gè)技術(shù)人員,我看到一項(xiàng)新技術(shù),總是喜歡琢磨琢磨它內(nèi)部是如何實(shí)現(xiàn)的。在對(duì) Ajax 有了初步認(rèn)識(shí)以后,自然想看看其內(nèi)部機(jī)制,但是令我失望的是,至少介紹 Ajax 內(nèi)部實(shí)現(xiàn)的文章少之又少,好容易找到一篇,卻也只是簡(jiǎn)單列了列一些 javascript 代碼,并且沒什么解釋,頗為郁悶。想想求人不如求己,況且自己研究的或許印象更深一些。于是找到了一個(gè) AjaxPro,下來琢磨琢磨,只是對(duì)于 JavaScript 我實(shí)在知之甚少,不明白之處依然很多,不過還是想寫出來,拋磚引玉,望高人們不吝指教。 一、使用的例子 本文使用的例子很簡(jiǎn)單,一個(gè)文本框,在其中敲入文字之后,下方就顯示該文字并加上一個(gè)“(Hello from server)”。源碼如下(有刪節(jié)): 1
![]() <% @ 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>選用這個(gè)例子,是因?yàn)樗容^簡(jiǎn)單,沒有相關(guān)的 C# 文件。首先看看第17行,咦?怎么這個(gè) form 里啥都沒有?既然什么都沒有?刪掉它行不行?不行,絕對(duì)不行!看看網(wǎng)頁打開后,這一行被擴(kuò)展成了什么? 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 ![]() 請(qǐng)注意這里鏈入的兩個(gè) javascript 文件,它們是 Ajax 得以運(yùn)行的基礎(chǔ)!刪掉 form 那一行,它們就不會(huì)出現(xiàn),自然不行了。這兩行是如何產(chǎn)生的?那就是頁面代碼的第4至7行的 PageLoad 函數(shù)的功勞了。 好,那這兩個(gè) javascript 文件我們能看到不?看上去它們是服務(wù)端的,并且事實(shí)上是服務(wù)端動(dòng)態(tài)生成的。不過稍有些了解瀏覽器工作原理的人就會(huì)知道,到 Local Settings 下的 Temporary Internet Files 目錄下去找,肯定是有的,因?yàn)闉g覽器下載頁面的時(shí)候會(huì)把與頁面相關(guān)的文件都下過來。 二、Ajax ClientScript 的執(zhí)行總體流程 好,有了源頁面代碼,又有了兩個(gè) ClientScript 文件,我們就可以分析客戶端的執(zhí)行流程了。以下是我畫的一張簡(jiǎn)單的流程圖: ![]() 我們一個(gè)一個(gè)地來分析。 三、HTML頁面做了什么? 第一步,當(dāng)我們?cè)?TextBox 里輸入字符后,將會(huì)觸發(fā) onkeyup 事件。它要執(zhí)行 doTest1 方法。見頁面代碼里的第22行。 第二步,doTest1 方法使用 setTimeout 函數(shù),設(shè)定了 100 毫秒后,執(zhí)行 doTest1_next 方法。見頁面代碼里的第34行。 第三步,doTest1_next 方法調(diào)用了 ASP.KeyPressDemo.EchoInput 方法,它帶有兩個(gè)參數(shù),第一個(gè)是我們?cè)谖谋究蛑休斎氲闹?,?dāng)然是個(gè)字符串類型的了;第二個(gè)則是一個(gè) callback 函數(shù),請(qǐng)留心這個(gè)函數(shù),它將于整個(gè)流程的最后執(zhí)行。 好,我們知道頁面的客戶端無外乎就是 HTML 和 JavaScript,雖然 ASP.KeyPressDemo.EchoInput 方法酷似頁面里我們自己用 C# 寫的函數(shù),但可以肯定的是它絕對(duì)是用 JavaScript 實(shí)現(xiàn)的。在哪兒呢?嗯,在我們從 Temporary Internet Files 目錄下找到的 ASP.KeyPressDemo,App_Web_vxhzzzxr.ashx 里。 四、ASP.KeyPressDemo,App_Web_vxhzzzxr.ashx 的實(shí)現(xiàn) 這個(gè)文件很小,以下是它的全部源碼: 1
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 其實(shí)是在這里用 JavaScript 定義的 ASP.KeyPressDemo_class 類的實(shí)例,EchoInput 則是它的一個(gè)方法。注意一下每3行,我們看到這個(gè)類是從 AjaxPro.Request 類繼承的。什么什么?繼承?有沒有搞錯(cuò)?JavaScript 什么時(shí)候開始面向?qū)ο罅硕皇腔趯?duì)象了?先擺下這個(gè)疑問,我們繼續(xù)往下看。 EchoInput 方法的實(shí)現(xiàn)很簡(jiǎn)單,就是調(diào)用了一個(gè) Invoke 方法。嗯,這個(gè)方法想必是從 AjaxPro.Request 類“繼承”下來的。那它定義在哪兒?是了,還有一個(gè) core.ashx 呢,它才是真正客戶端實(shí)現(xiàn) Ajax 技術(shù)的主角!這個(gè)文件太大,我們還是依照函數(shù)調(diào)用順序慢慢來拆解罷。 五、Invoke 函數(shù) Invoke 函數(shù)是核心所在,前面我畫的流程圖中已經(jīng)簡(jiǎn)單地描述了它的主要流程。不過這個(gè)函數(shù)太重要了,這里還是列出它的全部源碼: 1
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 ![]()
六、解釋“繼承” 1
![]() Object.extend = function(destination, source) {2 ![]() for(property in source) {3 destination[property] = source[property];4 }5 return destination;6 }7 ![]() 哈哈,所謂的“繼承”,其實(shí)只是個(gè)屬性拷貝而已。 前面我們看到了 this.xmlHttp 大顯神威。那么它是哪兒來的?看看 AjaxPro.Request 類的 initialize 函數(shù)吧(有刪節(jié)): 1
![]() initialize: function(url) {2 this.xmlHttp = new XMLHttpRequest();3 }4 ![]() 是了,xmlHttp 只是 XMLHttpRequest 的一個(gè)實(shí)例。那么 XMLHttpRequest 的定義呢? 1
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 ![]() 哦,原來是在這里真正創(chuàng)建的。說到底還是一個(gè) ActiveXObject 啊。關(guān)于這個(gè)本文也不再多提。不過代碼中還需要注意的一點(diǎn)是, 1
![]() 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,那么清除掉超時(shí)處理函數(shù),處理 onLoading 事件,最后使用 callback 調(diào)用 createResponse 函數(shù)。還記得如果不是異步的話,createResponse 將會(huì)直接調(diào)用而不是通過 doStateChange 吧。 1
![]() 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 是加過密的,這里就需要對(duì) responseText 進(jìn)行解密。完了之后得到 r.value,r 將會(huì)被返回并提供給 callback 函數(shù)。本例中將最終傳回 doTest1_callback,r 被傳入它的 res 參數(shù)。最后更新文本框下的字符串,整個(gè) Ajax ClientScript 的流程就差不多是完成了。 |
|
|