這個(gè)名字代表了異步JavaScript+XMLHTTPRequest,并且意味著(zhù)你可以在基于瀏覽器的JavaScript和服務(wù)器之間建立套接字通訊。其實(shí)AJAX并不是一種新技術(shù),而是已經(jīng)成功地用于現代瀏覽器中的若干成功技術(shù)的可能性組合。所有的AJAX應用程序實(shí)現了一種“豐富的”UI——這是通過(guò)JavaScript操作HTML文檔對象模型并且經(jīng)由XMLHttpRequest實(shí)現的精確定位的數據檢索來(lái)實(shí)現的。典型的示例AJAX應用程序是Google Labs(http://labs.google.com)的Google Maps和Google Suggest。這些應用程序現場(chǎng)監視用戶(hù)輸入并且提供實(shí)時(shí)的頁(yè)面更新。最重要的是,在用戶(hù)通過(guò)地圖導航或輸入一個(gè)查找字符串的同時(shí),這些事件不需要刷新頁(yè)面。
事實(shí)上,支持這些令人感到驚訝的應用的技術(shù)已經(jīng)出現一段時(shí)間了,盡管它們要求復雜的技能以及使用瀏覽器的技巧。一些專(zhuān)利產(chǎn)品就提供了相似的能力——如Macromedia Flash插件,Java Applets或.NET運行時(shí)——在達到實(shí)用上已經(jīng)有一段時(shí)間了。把一種可與服務(wù)器通話(huà)的腳本組件引入到瀏覽器中的思想早在IE 5.0中就已經(jīng)存在。Firefox和其它流行的瀏覽器也加入到瀏覽器大軍中并以一種內置對象形式支持XMLHTTPRequest。隨著(zhù)跨平臺瀏覽器的出現,這些技術(shù)得到了認可并在2004年3月一家稱(chēng)為Adaptive Path的公司中正式提出了AJAX。
簡(jiǎn)而言之,由于來(lái)自于Google的支持和安裝了一點(diǎn)可用的瀏覽器技術(shù),加上為了一種"更好的用戶(hù)體驗",每個(gè)人都在把客戶(hù)端技術(shù)添加到Web應用程序上。
二. AJAX與傳統應用程序的區別
一個(gè)傳統Web應用程序模型實(shí)際上是一種基本的事件——用戶(hù)被迫提交表單以實(shí)現頁(yè)面交換。也就是說(shuō),表單提交和頁(yè)面傳送無(wú)法得到保證:還有更壞的情形——用戶(hù)需要再次點(diǎn)擊。這與AJAX截然不同-——數據跨過(guò)線(xiàn)路而不是完整的HTML頁(yè)面傳輸。這種數據交換是經(jīng)由特定的瀏覽器對象:XMLHttpRequest實(shí)現的;再由適當的邏輯來(lái)處理每個(gè)數據請求的結果,頁(yè)面的特定區域而不是完整的頁(yè)面被更新。結果是更快的速度,更少的擁擠和更好的信息傳送控制。
傳統型"click-refresh"Web應用程序強迫用戶(hù)中斷工作過(guò)程而等待頁(yè)面的重裝。通過(guò)引入AJAX技術(shù),一個(gè)客戶(hù)端腳本能夠異步地與服務(wù)器通話(huà),而用戶(hù)仍能保持輸入數據。除了對用戶(hù)透明之外,這樣的異步意味著(zhù)服務(wù)器可以有更多時(shí)間來(lái)處理請求。
傳統Web應用程序把所有的處理代理到服務(wù)器并且強迫服務(wù)器進(jìn)行狀態(tài)管理。AJAX允許靈活劃分應用程序邏輯以及客戶(hù)和服務(wù)器之間的狀態(tài)管理。這就消除了一種"click-refresh"依賴(lài)性并且提供更好的服務(wù)器可伸縮性。當該狀態(tài)存儲在客戶(hù)端,你就不必跨越服務(wù)器來(lái)維持會(huì )話(huà)或保存/結束狀態(tài)-其使用期限是由客戶(hù)端來(lái)定義的。
三. AJAX——分布式的MVC
盡管AJAX應用程序依靠JavaScript來(lái)實(shí)現描述層,然而處理能力和知識庫仍然存在于服務(wù)器上。此時(shí),AJAX應用程序大量的與J2EE服務(wù)器通訊——把數據輸入/輸出Web服務(wù)和servlets。具有基于A(yíng)JAX的描述層的J2EE應用程序和標準J2EE應用程序之間的區別首先在于,MVC是通過(guò)線(xiàn)路分布的。通過(guò)使用AJAX,視圖是本地的,而模型和控制器是分布式的——這使得開(kāi)發(fā)者能夠靈活地決定哪些部件會(huì )是基于客戶(hù)端的。具體地說(shuō),本地視圖通過(guò)巧妙地操作HTML DOM而生成圖形;控制器局部地處理用戶(hù)輸入并且根據開(kāi)發(fā)者的判斷擴展到服務(wù)器的處理——經(jīng)由HTTP請求(Web服務(wù),XML/RPC或其它)實(shí)現;模型的遠程部分是根據客戶(hù)端需要而下載的以達到實(shí)時(shí)更新客戶(hù)端頁(yè)面;并且狀態(tài)是在客戶(hù)端收集的。
在以后的AJAX文章中,我們將比較深入地討論這里的每一種組件并提供有關(guān)它們聯(lián)合在一起進(jìn)行應用的示例?,F在,先不多說(shuō),讓我們詳細地分析一個(gè)簡(jiǎn)單的AJAX示例。
四. 郵政區號校驗和查詢(xún)
我們將創(chuàng )建一個(gè)包含三個(gè)INPUT字段(Zip,City和State)的HTML頁(yè)面。我們將保證,只要用戶(hù)輸入郵政區號的前三個(gè)數字,該頁(yè)面上的字段就會(huì )用第一個(gè)匹配的狀態(tài)值填充。一旦用戶(hù)輸入了所有五位郵政區號數,我們將立即決定和填充相應的城市。如果郵政區號無(wú)效(在服務(wù)器的數據庫沒(méi)有找到),那么我們將把郵政區號的邊界設置為紅色。這樣的可視化線(xiàn)索有助于用戶(hù)并且在現代瀏覽器中已經(jīng)成為一種標準(作為一實(shí)例,當Firefox找到一個(gè)HTML頁(yè)面中的匹配關(guān)鍵字時(shí),它會(huì )高亮與你在瀏覽器查找域輸入的內容一致的部分)。
讓我們首先創(chuàng )建一個(gè)簡(jiǎn)單的包含三個(gè)INPUT字段的HTML:zip,city和state。請注意,一旦一個(gè)字符輸入進(jìn)郵政區號字段域中,即調用方法zipChanged()。JavaScript函數zipChanged()(見(jiàn)下)在當zip長(cháng)度為3時(shí)調用函數updateState(),而在當zip長(cháng)度為5時(shí)調用函數up-dateCity()。而updateCity()和updateState()把大部分的工作代理到另一個(gè)函數ask()。
Zip:<input id="zipcode" type="text" maxlength="5" onKeyUp="zipChanged()" HTTPRequest = function () { function ask(url, fieldToFill, lookupField) { function handleHttpResponse(http, fieldToFill, lookupField) { 為ask()所使用的HttpRequest()函數(見(jiàn)上)是一跨瀏覽器的XMLHTTPRequest的一個(gè)實(shí)例的構造器;稍后我們將分析它。到目前為止,請注意對于handleResponse()的調用是如何用一匿名函數包裝的-這個(gè)函數是function(){handleHttpResponse(http,fieldToFill, lookupField)}。 該函數的代碼是動(dòng)態(tài)創(chuàng )建的并且在每次我們給http.onreadstatechange屬性賦值時(shí)被編譯。結果,JavaScript創(chuàng )建一個(gè)指向上下文(所有的變量都可以存取正在結束的方法-ask())的指針。這樣以來(lái),匿名函數和handleResponse()就能夠被保證充分存取所有的上下文宿主的變量,直至到匿名函數的參考被垃圾回收站收集為止。換句話(huà)說(shuō),無(wú)論何時(shí)我們的匿名函數被調用,它都能無(wú)縫地參考request,fieldToFill和lookupField變量,就象它們是全局的一樣。而且,每次ask()調用都將創(chuàng )建環(huán)境的一個(gè)獨立拷貝,并且此時(shí)這些變量中保存有該函數將結束時(shí)的值。 現在,讓我們分析一下函數handleResponse()。既然它能夠在請求處理的不同狀態(tài)下激活,那么該函數將忽略所有的情形-除了該請求處理完成之外-這相應于request.readyState屬性等于4("Completed")。此時(shí),該函數讀取服務(wù)器的響應文本。與它的名字所暗示的相反,XmlHttpRequest的輸入和輸出都不必限于XML格式。特別地,我們的resolveZip.jsp(見(jiàn)源碼中的列表1)返回普通文本。如果返回值為"unknown",那么該函數將假定郵政區號是無(wú)效的并且把查找字段(zip)邊界顏色置為紅色。否則,返回值被用于填充字段state或city,并且zip的邊界被賦予一種缺省顏色。 讓我們返回到我們的XMLHTTPRequest的跨瀏覽器實(shí)現。最后一個(gè)列表包含一個(gè)HttpRequest()函數-它向上兼容于IE5.0和Mozilla 1.8/FireFox。為簡(jiǎn)化起見(jiàn),我們只創(chuàng )建一個(gè)微軟XMLHTTPRequest對象,而且如果創(chuàng )建失敗,我們假定它是Firefox/Mozilla。 該函數的核心是XMLHTTPRequest-這是一個(gè)本機瀏覽器對象,它為包括HTTP協(xié)議的任何東西與服務(wù)器之間的通訊提供方便。它允許指定任何HTTP動(dòng)詞,頭部和有效載荷,并且能夠以異步或同步方式工作。不需要下載也不需要安裝任何插件-盡管在IE的情形下,XMLHTTPRequest是一個(gè)集成到瀏覽器內部的ActiveX。因而,"Run ActiveX Control and Plugins"默認IE權限應該正好適合使用它。 最重要的是,XMLHTTPRequest允許一個(gè)到服務(wù)器的RPC風(fēng)格的編程查詢(xún)而不需要任何頁(yè)面刷新。它以一種可預測的,可控制的方式來(lái)實(shí)現此-提供了到HTTP協(xié)議的所有細節的完整存取-包括頭部和數據的任何定制格式。在以后的文章中,我們將向你展示其它一些業(yè)界協(xié)議-你可以在這些傳輸協(xié)議(如Web服務(wù)和XML-RPC)之上運行-它們極大地簡(jiǎn)化大規模應用程序的開(kāi)發(fā)和維護。 五.服務(wù)器端邏輯 最后,服務(wù)器端的resolveZip.jsp被從函數ask()中調用(見(jiàn)所附源碼中的列表1)。這個(gè)resolveZip.jsp在兩種由當前的郵政區號長(cháng)度所區分的獨立的場(chǎng)所下被調用(見(jiàn)zipChanged()函數)。請求參數lookupType的值或者是state或者是city。為簡(jiǎn)化起見(jiàn),我們將假定,兩個(gè)文件state.properties和city.properties都位于服務(wù)器中C驅動(dòng)器的根目錄下。resolveZip.jsp邏輯負責用適當的預裝載的文件返回查找值。 我們的支持AJAX的頁(yè)面現在已經(jīng)準備好了。 六.遠程腳本技術(shù)-一種可選方法 一些更舊的AJAX實(shí)現是基于所謂的遠程腳本技術(shù)。這種思想是,用戶(hù)的行為導致經(jīng)由IFRAME對服務(wù)器進(jìn)行查詢(xún),而服務(wù)器用JavaScript作出響應,該腳本一旦到達客戶(hù)端立即被執行。這與XMLHttpRequest方法相比存在較大的區別,在后者情況下,服務(wù)器響應數據而客戶(hù)端解釋數據。其好處是這種解決方案支持更舊的瀏覽器。 基于IFRAME示例的HTML部分(見(jiàn)所附源碼中的列表2)與我們在XMLHTTPRequest場(chǎng)合下所用的極相似,但是這次我們將引入另外一個(gè)IFRAME元素-controller: Zip:<input id="zipcode" type="text" maxlength="5" onKeyUp="zipChanged()" 我們保持每次擊鍵都調用zipChanged()一次,但是這一次,從zipChanged()中被調用的函數ask()(見(jiàn)所附源碼中的列表3)負責設置IFRAME的src屬性,而不是調用一個(gè)XMLHTTPRequest: function ask(url, fieldToFill, lookupField){ 服務(wù)器端邏輯由一個(gè)粗略的resolveZip.jsp(見(jiàn)所附源碼中的列表4)所描述。它與它的XMLHTTPRequest對應物相區別-它返回JavaScript語(yǔ)句,這些語(yǔ)句設置變量字段lookup和city的全局值,而且一旦它到達瀏覽器即從全局窗口的執行上下文中調用函數response()。 函數response()是一修改版本的handleResponse()-這一函數可以免于處理未完成的請求(詳見(jiàn)本文所附源碼中的列表2)。 七. 難題 為簡(jiǎn)化起見(jiàn),讓我們"俯看"一下在我們的示例代碼中的一些重要的問(wèn)題: 1.事實(shí)-XMLHTTPRequest對象實(shí)例和回調函數調用在被使用以后并沒(méi)被破壞-在每次調用后這有可能導致內存泄漏。適當編寫(xiě)的代碼應該破壞或重用對象池中的這些實(shí)例。而且,客戶(hù)端必須使用與服務(wù)器軟件相同的對象管理技術(shù)。 2.在大多數情況下,錯誤往往得不到有效處理。例如,在方法ask()中對request.open()的調用可能引發(fā)一個(gè)異常,這是必須要捕獲和處理的,即使在瀏覽器中沒(méi)有設置JavaScript異常自動(dòng)捕獲功能。而handleResponse()函數又是另外一個(gè)例子。它必須要為可能的服務(wù)器端和通訊錯誤而檢查headers和responseText值。在發(fā)生錯誤的情況下,它必須盡力恢復并/或者報告錯誤。正確開(kāi)發(fā)的AJAX應用程序要盡可能避免"提交"松散的數據,因為往往存在線(xiàn)路斷開(kāi)和其它低級通訊的問(wèn)題-所以這些程序必須建立一個(gè)強壯的和自恢復的框架為此提供支持。 3.當前服務(wù)器端框架提供相當多的功能-它們可以與一種自由刷新方法和諧相處。例如,讓我們考慮一個(gè)定制的在指定時(shí)間內的服務(wù)器端認證的問(wèn)題。在這種情況下,我們必須攔截到XMLHTTPRequest調用的安全系統響應,顯示登錄屏幕,然后在用戶(hù)被認證后重新發(fā)出請求。 所有的這些問(wèn)題只是一些典型的用低級API工作的任何應用程序代碼,而且所有這些問(wèn)題都能被解決。好消息是,解決這些問(wèn)題所需要的技術(shù)十分相似于大多數Java開(kāi)發(fā)技術(shù),如Web服務(wù),定制標簽和XML/XSLT。唯一的區別在于,現在這些技術(shù)以下列形式用于客戶(hù)端: ·Web服務(wù)-使用SOAP/REST/RPC等簡(jiǎn)單通訊標準 ·客戶(hù)端定制標簽-打包豐富的客戶(hù)端控件并集成AJAX功能 ·數據操作-基于XML和基于XSLT技術(shù) 八. 小結 AJAX方法能夠向人們提供一種與桌面應用程序相同的豐富的互聯(lián)網(wǎng)體驗。但是,我們必須有選擇地使用AJAX技術(shù),如當你仍在線(xiàn)購物時(shí),你絕對不想讓你的信用卡通過(guò)后臺處理就悄悄地開(kāi)始付款。AJAX會(huì )成為一種持續的動(dòng)力嗎?我們當然希望這樣。在過(guò)去的五年時(shí)間內我們一直在努力開(kāi)發(fā)AJAX應用程序并且能證明它是健全并且很有效的。然而,它要求一個(gè)開(kāi)發(fā)者必須精通大量技術(shù)而不是在傳統的"click-refresh"Web應用程序中所使用的那些。
style="width:60"/>
City: <input id="city" disabled maxlength="32" style="width:160"/>
State:<input id="state" disabled maxlength="2" style="width:30"/>
<script src="xmlhttp.js"></script>
<script>
var zipField = null;
function zipChanged(){
zipField = document.getElementById("zipcode")
var zip = zipField.value;
zip.length == 3?updateState(zip):zip.length == 5?updateCity(zip):"";
}
function updateState(zip) {
var stateField = document.getElementById("state");
ask("resolveZip.jsp?lookupType=state&zip="+zip, stateField, zipField);
}
function updateCity(zip) {
var cityField = document.getElementById("city");
ask("resolveZip.jsp? lookupType=city&zip="+zip, cityField, zipField);
}
</script>
函數ask()與服務(wù)器進(jìn)行通訊并分配一個(gè)回調函數來(lái)處理服務(wù)器的響應(見(jiàn)下列代碼)。后面,我們將分析具有雙重特點(diǎn)的resolveZip.jsp的內容-它根據zip字段中的字符數查找city或state信息。重要的是,ask()使用了具有異步特點(diǎn)的XmlHttpRequest,這樣填充state和city字段或著(zhù)色zip字段邊界就可以不必減慢數據入口而得以實(shí)現。首先,我們調用request.open()-它用服務(wù)器打開(kāi)套接字頻道,使用一個(gè)HTTP動(dòng)詞(GET或POST)作為第一個(gè)參數并且以數據提供者的URL作為第二個(gè)參數。request.open()的最后一個(gè)參數被設置為true-它指示該請求的異步特性。注意,該請求還沒(méi)有被提交。隨著(zhù)對request.send()的調用,開(kāi)始提交-這可以為POST提供任何必要的有效載荷。在使用異步請求時(shí),我們必須使用request.onreadystatechanged屬性來(lái)分配請求的回調函數。(如果請求是同步的話(huà),我們應該能夠在調用request.send之后立即處理結果,但是我們也有可能阻斷用戶(hù),直到該請求完成為止。)
var xmlhttp=null;
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (_e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (_E) { }
}
if (!xmlhttp && typeof XMLHttpRequest != ‘undefined‘) {
try {
xmlhttp = new XMLHttpRequest();
} catch (e) {
xmlhttp = false;
}
}
return xmlhttp;
}
var http = new HTTPRequest();
http.open("GET", url, true);
http.onreadystatechange = function (){ handleHttpResponse(http, fieldToFill,lookupField)};
http.send(null);
}
if (http.readyState == 4) {
result = http.responseText;
if ( -1 != result.search("null") ) {
lookupField.style.borderColor = "red";
fieldToFill.value = "";
} else {
lookupField.style.borderColor = "";
fieldToFill.value = result;
}
}
}
XMLHttpRequest-傳輸對象
style="width:60" size="20"/>
City: <input id="city" disabled maxlength="32" style="width:160" size="20"/>
State:<input id="state" disabled maxlength="2" style="width:30" size="20"/>
<iframe id="controller" style="visibility:hidden;width:0;height:0"></iframe>
var controller = document.getElementById("controller");
controller.src= url+"&field="+fieldToFill.id+"&zip="+lookupField.id;
}
聯(lián)系客服