藍晶石

          BlogJava 首頁 新隨筆 聯系 聚合 管理
            10 Posts :: 0 Stories :: 5 Comments :: 0 Trackbacks

          2005年12月29日 #

          一. 什么是AJAX?

            這個名字代表了異步JavaScript+XMLHTTPRequest,并且意味著你可以在基于瀏覽器的JavaScript和服務器之間建立套接字通訊。其實AJAX并不是一種新技術,而是已經成功地用于現代瀏覽器中的若干成功技術的可能性組合。所有的AJAX應用程序實現了一種“豐富的”UI——這是通過JavaScript操作HTML文檔對象模型并且經由XMLHttpRequest實現的精確定位的數據檢索來實現的。典型的示例AJAX應用程序是Google Labs(http://labs.google.com)的Google Maps和Google Suggest。這些應用程序現場監視用戶輸入并且提供實時的頁面更新。最重要的是,在用戶通過地圖導航或輸入一個查找字符串的同時,這些事件不需要刷新頁面。

            事實上,支持這些令人感到驚訝的應用的技術已經出現一段時間了,盡管它們要求復雜的技能以及使用瀏覽器的技巧。一些專利產品就提供了相似的能力——如Macromedia Flash插件,Java Applets或.NET運行時——在達到實用上已經有一段時間了。把一種可與服務器通話的腳本組件引入到瀏覽器中的思想早在IE 5.0中就已經存在。Firefox和其它流行的瀏覽器也加入到瀏覽器大軍中并以一種內置對象形式支持XMLHTTPRequest。隨著跨平臺瀏覽器的出現,這些技術得到了認可并在2004年3月一家稱為Adaptive Path的公司中正式提出了AJAX。

            簡而言之,由于來自于Google的支持和安裝了一點可用的瀏覽器技術,加上為了一種"更好的用戶體驗",每個人都在把客戶端技術添加到Web應用程序上。

            二. AJAX與傳統應用程序的區別

            一個傳統Web應用程序模型實際上是一種基本的事件——用戶被迫提交表單以實現頁面交換。也就是說,表單提交和頁面傳送無法得到保證:還有更壞的情形——用戶需要再次點擊。這與AJAX截然不同-——數據跨過線路而不是完整的HTML頁面傳輸。這種數據交換是經由特定的瀏覽器對象:XMLHttpRequest實現的;再由適當的邏輯來處理每個數據請求的結果,頁面的特定區域而不是完整的頁面被更新。結果是更快的速度,更少的擁擠和更好的信息傳送控制。

            傳統型"click-refresh"Web應用程序強迫用戶中斷工作過程而等待頁面的重裝。通過引入AJAX技術,一個客戶端腳本能夠異步地與服務器通話,而用戶仍能保持輸入數據。除了對用戶透明之外,這樣的異步意味著服務器可以有更多時間來處理請求。

            傳統Web應用程序把所有的處理代理到服務器并且強迫服務器進行狀態管理。AJAX允許靈活劃分應用程序邏輯以及客戶和服務器之間的狀態管理。這就消除了一種"click-refresh"依賴性并且提供更好的服務器可伸縮性。當該狀態存儲在客戶端,你就不必跨越服務器來維持會話或保存/結束狀態-其使用期限是由客戶端來定義的。

            三. AJAX——分布式的MVC

            盡管AJAX應用程序依靠JavaScript來實現描述層,然而處理能力和知識庫仍然存在于服務器上。此時,AJAX應用程序大量的與J2EE服務器通訊——把數據輸入/輸出Web服務和servlets。具有基于AJAX的描述層的J2EE應用程序和標準J2EE應用程序之間的區別首先在于,MVC是通過線路分布的。通過使用AJAX,視圖是本地的,而模型和控制器是分布式的——這使得開發者能夠靈活地決定哪些部件會是基于客戶端的。具體地說,本地視圖通過巧妙地操作HTML DOM而生成圖形;控制器局部地處理用戶輸入并且根據開發者的判斷擴展到服務器的處理——經由HTTP請求(Web服務,XML/RPC或其它)實現;模型的遠程部分是根據客戶端需要而下載的以達到實時更新客戶端頁面;并且狀態是在客戶端收集的。

            在以后的AJAX文章中,我們將比較深入地討論這里的每一種組件并提供有關它們聯合在一起進行應用的示例。現在,先不多說,讓我們詳細地分析一個簡單的AJAX示例。

            四. 郵政區號校驗和查詢

            我們將創建一個包含三個INPUT字段(Zip,City和State)的HTML頁面。我們將保證,只要用戶輸入郵政區號的前三個數字,該頁面上的字段就會用第一個匹配的狀態值填充。一旦用戶輸入了所有五位郵政區號數,我們將立即決定和填充相應的城市。如果郵政區號無效(在服務器的數據庫沒有找到),那么我們將把郵政區號的邊界設置為紅色。這樣的可視化線索有助于用戶并且在現代瀏覽器中已經成為一種標準(作為一實例,當Firefox找到一個HTML頁面中的匹配關鍵字時,它會高亮與你在瀏覽器查找域輸入的內容一致的部分)。

            讓我們首先創建一個簡單的包含三個INPUT字段的HTML:zip,city和state。請注意,一旦一個字符輸入進郵政區號字段域中,即調用方法zipChanged()。JavaScript函數zipChanged()(見下)在當zip長度為3時調用函數updateState(),而在當zip長度為5時調用函數up-dateCity()。而updateCity()和updateState()把大部分的工作代理到另一個函數ask()。

          Zip:<input id="zipcode" type="text" maxlength="5" onKeyUp="zipChanged()"
          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()與服務器進行通訊并分配一個回調函數來處理服務器的響應(見下列代碼)。后面,我們將分析具有雙重特點的resolveZip.jsp的內容-它根據zip字段中的字符數查找city或state信息。重要的是,ask()使用了具有異步特點的XmlHttpRequest,這樣填充state和city字段或著色zip字段邊界就可以不必減慢數據入口而得以實現。首先,我們調用request.open()-它用服務器打開套接字頻道,使用一個HTTP動詞(GET或POST)作為第一個參數并且以數據提供者的URL作為第二個參數。request.open()的最后一個參數被設置為true-它指示該請求的異步特性。注意,該請求還沒有被提交。隨著對request.send()的調用,開始提交-這可以為POST提供任何必要的有效載荷。在使用異步請求時,我們必須使用request.onreadystatechanged屬性來分配請求的回調函數。(如果請求是同步的話,我們應該能夠在調用request.send之后立即處理結果,但是我們也有可能阻斷用戶,直到該請求完成為止。)

          HTTPRequest = function () {
           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;
          }

          function ask(url, fieldToFill, lookupField) {
           var http = new HTTPRequest();
           http.open("GET", url, true);
           http.onreadystatechange = function (){ handleHttpResponse(http, fieldToFill,lookupField)};
           http.send(null);
          }

          function handleHttpResponse(http, fieldToFill, lookupField) {
           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;
            }
           }
          }

            為ask()所使用的HttpRequest()函數(見上)是一跨瀏覽器的XMLHTTPRequest的一個實例的構造器;稍后我們將分析它。到目前為止,請注意對于handleResponse()的調用是如何用一匿名函數包裝的-這個函數是function(){handleHttpResponse(http,fieldToFill, lookupField)}。

            該函數的代碼是動態創建的并且在每次我們給http.onreadstatechange屬性賦值時被編譯。結果,JavaScript創建一個指向上下文(所有的變量都可以存取正在結束的方法-ask())的指針。這樣以來,匿名函數和handleResponse()就能夠被保證充分存取所有的上下文宿主的變量,直至到匿名函數的參考被垃圾回收站收集為止。換句話說,無論何時我們的匿名函數被調用,它都能無縫地參考request,fieldToFill和lookupField變量,就象它們是全局的一樣。而且,每次ask()調用都將創建環境的一個獨立拷貝,并且此時這些變量中保存有該函數將結束時的值。

            現在,讓我們分析一下函數handleResponse()。既然它能夠在請求處理的不同狀態下激活,那么該函數將忽略所有的情形-除了該請求處理完成之外-這相應于request.readyState屬性等于4("Completed")。此時,該函數讀取服務器的響應文本。與它的名字所暗示的相反,XmlHttpRequest的輸入和輸出都不必限于XML格式。特別地,我們的resolveZip.jsp(見源碼中的列表1)返回普通文本。如果返回值為"unknown",那么該函數將假定郵政區號是無效的并且把查找字段(zip)邊界顏色置為紅色。否則,返回值被用于填充字段state或city,并且zip的邊界被賦予一種缺省顏色。

          XMLHttpRequest-傳輸對象

            讓我們返回到我們的XMLHTTPRequest的跨瀏覽器實現。最后一個列表包含一個HttpRequest()函數-它向上兼容于IE5.0和Mozilla 1.8/FireFox。為簡化起見,我們只創建一個微軟XMLHTTPRequest對象,而且如果創建失敗,我們假定它是Firefox/Mozilla。

            該函數的核心是XMLHTTPRequest-這是一個本機瀏覽器對象,它為包括HTTP協議的任何東西與服務器之間的通訊提供方便。它允許指定任何HTTP動詞,頭部和有效載荷,并且能夠以異步或同步方式工作。不需要下載也不需要安裝任何插件-盡管在IE的情形下,XMLHTTPRequest是一個集成到瀏覽器內部的ActiveX。因而,"Run ActiveX Control and Plugins"默認IE權限應該正好適合使用它。

            最重要的是,XMLHTTPRequest允許一個到服務器的RPC風格的編程查詢而不需要任何頁面刷新。它以一種可預測的,可控制的方式來實現此-提供了到HTTP協議的所有細節的完整存取-包括頭部和數據的任何定制格式。在以后的文章中,我們將向你展示其它一些業界協議-你可以在這些傳輸協議(如Web服務和XML-RPC)之上運行-它們極大地簡化大規模應用程序的開發和維護。

            五.服務器端邏輯

            最后,服務器端的resolveZip.jsp被從函數ask()中調用(見所附源碼中的列表1)。這個resolveZip.jsp在兩種由當前的郵政區號長度所區分的獨立的場所下被調用(見zipChanged()函數)。請求參數lookupType的值或者是state或者是city。為簡化起見,我們將假定,兩個文件state.properties和city.properties都位于服務器中C驅動器的根目錄下。resolveZip.jsp邏輯負責用適當的預裝載的文件返回查找值。

            我們的支持AJAX的頁面現在已經準備好了。

            六.遠程腳本技術-一種可選方法

            一些更舊的AJAX實現是基于所謂的遠程腳本技術。這種思想是,用戶的行為導致經由IFRAME對服務器進行查詢,而服務器用JavaScript作出響應,該腳本一旦到達客戶端立即被執行。這與XMLHttpRequest方法相比存在較大的區別,在后者情況下,服務器響應數據而客戶端解釋數據。其好處是這種解決方案支持更舊的瀏覽器。

            基于IFRAME示例的HTML部分(見所附源碼中的列表2)與我們在XMLHTTPRequest場合下所用的極相似,但是這次我們將引入另外一個IFRAME元素-controller:

          Zip:<input id="zipcode" type="text" maxlength="5" onKeyUp="zipChanged()"
          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>

            我們保持每次擊鍵都調用zipChanged()一次,但是這一次,從zipChanged()中被調用的函數ask()(見所附源碼中的列表3)負責設置IFRAME的src屬性,而不是調用一個XMLHTTPRequest:

          function ask(url, fieldToFill, lookupField){
           var controller = document.getElementById("controller");
           controller.src= url+"&field="+fieldToFill.id+"&zip="+lookupField.id;
          }

            服務器端邏輯由一個粗略的resolveZip.jsp(見所附源碼中的列表4)所描述。它與它的XMLHTTPRequest對應物相區別-它返回JavaScript語句,這些語句設置變量字段lookup和city的全局值,而且一旦它到達瀏覽器即從全局窗口的執行上下文中調用函數response()。

            函數response()是一修改版本的handleResponse()-這一函數可以免于處理未完成的請求(詳見本文所附源碼中的列表2)。

            七. 難題

            為簡化起見,讓我們"俯看"一下在我們的示例代碼中的一些重要的問題:

            1.事實-XMLHTTPRequest對象實例和回調函數調用在被使用以后并沒被破壞-在每次調用后這有可能導致內存泄漏。適當編寫的代碼應該破壞或重用對象池中的這些實例。而且,客戶端必須使用與服務器軟件相同的對象管理技術。

            2.在大多數情況下,錯誤往往得不到有效處理。例如,在方法ask()中對request.open()的調用可能引發一個異常,這是必須要捕獲和處理的,即使在瀏覽器中沒有設置JavaScript異常自動捕獲功能。而handleResponse()函數又是另外一個例子。它必須要為可能的服務器端和通訊錯誤而檢查headers和responseText值。在發生錯誤的情況下,它必須盡力恢復并/或者報告錯誤。正確開發的AJAX應用程序要盡可能避免"提交"松散的數據,因為往往存在線路斷開和其它低級通訊的問題-所以這些程序必須建立一個強壯的和自恢復的框架為此提供支持。

            3.當前服務器端框架提供相當多的功能-它們可以與一種自由刷新方法和諧相處。例如,讓我們考慮一個定制的在指定時間內的服務器端認證的問題。在這種情況下,我們必須攔截到XMLHTTPRequest調用的安全系統響應,顯示登錄屏幕,然后在用戶被認證后重新發出請求。

            所有的這些問題只是一些典型的用低級API工作的任何應用程序代碼,而且所有這些問題都能被解決。好消息是,解決這些問題所需要的技術十分相似于大多數Java開發技術,如Web服務,定制標簽和XML/XSLT。唯一的區別在于,現在這些技術以下列形式用于客戶端:

            ·Web服務-使用SOAP/REST/RPC等簡單通訊標準

            ·客戶端定制標簽-打包豐富的客戶端控件并集成AJAX功能

            ·數據操作-基于XML和基于XSLT技術

            八. 小結

            AJAX方法能夠向人們提供一種與桌面應用程序相同的豐富的互聯網體驗。但是,我們必須有選擇地使用AJAX技術,如當你仍在線購物時,你絕對不想讓你的信用卡通過后臺處理就悄悄地開始付款。AJAX會成為一種持續的動力嗎?我們當然希望這樣。在過去的五年時間內我們一直在努力開發AJAX應用程序并且能證明它是健全并且很有效的。然而,它要求一個開發者必須精通大量技術而不是在傳統的"click-refresh"Web應用程序中所使用的那些。

          posted @ 2005-12-29 17:10 kyanite 閱讀(219) | 評論 (0)編輯 收藏

          與任何技術一樣,使用Ajax在相當多的方面都可能范錯誤。我在這兒討論的問題目前都缺少解決方案,并將會隨著Ajax的成熟而解決或提高。隨著開發 Ajax應用經驗的不斷獲取,開發者社區中將會出現最好的實踐經驗與指導方針。

            1、XMLHttpRequest的有效性

            Ajax開發者面對的一個最大問題是當XMLHttpRequest不可用時如何反應。雖然大部分現代瀏覽器支持XMLHttpRequest,但還是有少量的用戶,他們的瀏覽器不能支持,或由于瀏覽器安全設置而阻止對XMLHttpRequest的使用。若你的Web應用發布于公司內部的 Intranet上,你很可能可以指定支持哪種瀏覽器,并可以確保XMLHttpRequest是可用的。若你在公共WEB上發布,則你必須意識到由于假定XMLHttpRequest是可用的,所有就阻止了老瀏覽器、手持設備瀏覽器等等用戶來使用你的系統。

            然而,你應該盡力保證應用系統“正常降級”使用,在系統中保留適用于不支持XMLHttpRequest的瀏覽器的功能。在購物車例子中,最好的方法是有一個Add to Cart按鈕,可以進行常規的提交處理,并刷新頁面來反映購物車狀態的變化。Ajax行衛可以在頁面被載入時通過JavaScript添加到頁面中,只在 XMLHttpRequest可用的情況下,為每個Add to Cart按鈕加上JavaScript處理函數。另一個方法是在用戶登錄時檢測XMLHttpRequest,再決定是提供Ajax版本還是常規基于 form提交的版本。

            2、可用性考慮

            圍繞著Ajax應用的大部分問題都是很普通的問題。例如,讓用戶知道他們的輸入已經被注冊并處理,是很重要的,因為在XMLHttpRequest處理過程中并不能提供通常的漏斗旋轉光標。一種方法是將“確認”按扭上的文本替換為“正在更新中…”,以避免用戶在等待響應時多次點擊按鈕。

            另一個問題是,用戶可能沒有注意到他們正在觀看的頁面已經被更新。可以通過使用各種視覺技巧來將用戶的眼光吸引到頁面的更新區域。還有一個問題是通過 Ajax更新頁面打斷了瀏覽器“退回前頁”按鈕的正常工作,地址欄中的URL不能反映頁面的全部狀態,并且不能使用書簽功能。參見Resource章節中列出的網站地址上的文章來了解更多Ajax應用關于可用性方面的問題。

            3、服務器負載

            使用Ajax界面代替傳統的基于form的界面可能戲劇性地增加傳遞到服務器的請求數量。例如,一個普通的Google搜索給服務器造成一次命中,并在用戶確認搜索表單時發生。然而,Google Suggest,將會試圖自動完成你的搜索詞,在用戶打字時將會往服務器發送多個請求。在開發一個Ajax應用時,要注意到你將會發送多少請求到用戶器端,以及服務器的負載指標。你可以通過在客戶端適當地緩存請求、與服務器響應來緩減負載壓力。你也應該在設計Ajax應用時盡量在客戶端處理更多的邏輯,而不用與服務器端通訊。

            4、處理異步

            一定要記住,沒有任何東西可以保證XMLHttpRequest將會按照它們被發送的順序來依次結束。實際上,你在設計系統時,腦子里應該始終假定它們不會按原來順序結束。在購物車例子中,使用了一個最后更新的時間戳來保證最新的數據不會被改寫。這個非常基本的方法可以在購物車場景中工作,但可能不能在其它情況下工作。在設計時刻就要考慮你該如何處理異步服務器響應。

            結論

            你現在應該對于Ajax的基本原則有了一個良好的了解,另外,你應該理解一些更高級的隨Ajax方法而來的設計問題。創建一個成功的Ajax應用需要一系列的方法—從JavaScript UI設計到服務器端架構—但是你現在應該已經具備了需要使用到的Ajax核心知識。

          posted @ 2005-12-29 16:01 kyanite 閱讀(172) | 評論 (0)編輯 收藏

          主站蜘蛛池模板: 吉隆县| 呼玛县| 彭山县| 金秀| 健康| 仙桃市| 库车县| 将乐县| 福鼎市| 冕宁县| 德钦县| 贺州市| 浦县| 邓州市| 扶绥县| 孝昌县| 富源县| 托克逊县| 定兴县| 阿图什市| 祁连县| 云浮市| 连城县| 朔州市| 宝兴县| 驻马店市| 益阳市| 满城县| 鄱阳县| 玛纳斯县| 湖口县| 思茅市| 潞城市| 郧西县| 化隆| 来安县| 漯河市| 万盛区| 大荔县| 无极县| 包头市|