Ajax 完整教程 第 1 頁 Ajax 簡介Ajax 由 HTML、JavaScript™ 技術、DHTML 和 DOM 組成,這一傑出的方法可以將笨拙的 Web 界面轉化成交互性的 Ajax 應用程式。本文的作者是一位 Ajax 專家,他演示了這些技術如何協同工作 —— 從總體概述到細節的討論 ...
Ajax 完整教程
第 1 頁 Ajax 簡介
Ajax 由 HTML、JavaScript™ 技術、DHTML 和 DOM 組成,這一傑出的方法可以將笨拙的 Web 界面轉化成交互性的 Ajax 應用程式。本文的作者是一位 Ajax 專家,他演示了這些技術如何協同工作 —— 從總體概述到細節的討論 —— 使高效的 Web 開發成為現實。他還揭開了 Ajax 核心概念的神秘面紗,包括 XMLHttpRequest 對象。
五年前,如果不知道 XML,您就是一隻無人重視的醜小鴨。十八個月前,Ruby 成了關註的中心,不知道 Ruby 的程式員只能坐冷板凳了。今天,如果想跟上最新的技術時尚,那您的目標就是 Ajax。
但是,Ajax 不僅僅 是一種時尚,它是一種構建網站的強大方法,而且不像學習一種全新的語言那樣困難。
但在詳細探討 Ajax 是什麼之前,先讓我們花幾分鐘瞭解 Ajax 做 什麼。目前,編寫應用程式時有兩種基本的選擇:
·桌面應用程式
·Web 應用程式
兩者是類似的,桌面應用程式通常以 CD 為介質(有時候可從網站下載)並完全安裝到您的電腦上。桌面應用程式可能使用互聯網下載更新,但運行這些應用程式的代碼在桌面電腦上。Web 應用程式運行在某處的 Web 伺服器上 —— 毫不奇怪,要通過 Web 瀏覽器訪問這種應用程式。
不過,比這些應用程式的運行代碼放在何處更重要的是,應用程式如何運轉以及如何與其進行交互。桌面應用程式一般很快(就在您的電腦上運行,不用等待互聯網連接),具有漂亮的用戶界面(通常和操作系統有關)和非凡的動態性。可以單擊、選擇、輸入、打開菜單和子菜單、到處巡游,基本上不需要等待。
另一方面,Web 應用程式是最新的潮流,它們提供了在桌面上不能實現的服務(比如 Amazon.com 和 eBay)。但是,伴隨著 Web 的強大而出現的是等待,等待伺服器響應,等待屏幕刷新,等待請求返回和生成新的頁面。
顯然這樣說過於簡略了,但基本的概念就是如此。您可能已經猜到,Ajax 嘗試建立桌面應用程式的功能和交互性,與不斷更新的 Web 應用程式之間的橋梁。可以使用像桌面應用程式中常見的動態用戶界面和漂亮的控制項,不過是在 Web 應用程式中。
還等什麼呢?我們來看看 Ajax 如何將笨拙的 Web 界面轉化成能迅速響應的 Ajax 應用程式吧。
老技術,新技巧
在談到 Ajax 時,實際上涉及到多種技術,要靈活地運用它必須深入瞭解這些不同的技術(本系列的頭幾篇文章將分別討論這些技術)。好消息是您可能已經非常熟悉其中的大部分技術,更好的是這些技術都很容易學習,並不像完整的編程語言(如 Java 或 Ruby)那樣困難。
下麵是 Ajax 應用程式所用到的基本技術:
·HTML 用於建立 Web 表單並確定應用程式其他部分使用的欄位。
·JavaScript 代碼是運行 Ajax 應用程式的核心代碼,幫助改進與伺服器應用程式的通信。
·DHTML 或 Dynamic HTML,用於動態更新表單。我們將使用 div、span 和其他動態 HTML 元素來標記 HTML。
·文檔對象模型 DOM 用於(通過 JavaScript 代碼)處理 HTML 結構和(某些情況下)伺服器返回的 XML。
Ajax 的定義
順便說一下,Ajax 是 Asynchronous JavaScript and XML(以及 DHTML 等)的縮寫。這個短語是 Adaptive Path 的 Jesse James Garrett 發明的(請參閱 參考資料),按照 Jesse 的解釋,這不是 個首字母縮寫詞。
我們來進一步分析這些技術的職責。以後的文章中我將深入討論這些技術,目前只要熟悉這些組件和技術就可以了。對這些代碼越熟悉,就越容易從對這些技術的零散瞭解轉變到真正把握這些技術(同時也真正打開了 Web 應用程式開發的大門)。
XMLHttpRequest 對象
要瞭解的一個對象可能對您來說也是最陌生的,即 XMLHttpRequest。這是一個 JavaScript 對象,創建該對象很簡單,如清單 1 所示。
清單 1. 創建新的 XMLHttpRequest 對象
<script language="javascript" type="text/javascript">
var xmlHttp = new XMLHttpRequest();
</script>
下一期文章中將進一步討論這個對象,現在要知道這是處理所有伺服器通信的對象。繼續閱讀之前,先停下來想一想:通過 XMLHttpRequest 對象與伺服器進行對話的是 JavaScript 技術。這不是一般的應用程式流,這恰恰是 Ajax 的強大功能的來源。
在一般的 Web 應用程式中,用戶填寫表單欄位並單擊 Submit 按鈕。然後整個表單發送到伺服器,伺服器將它轉發給處理表單的腳本(通常是 PHP 或 Java,也可能是 CGI 進程或者類似的東西),腳本執行完成後再發送回全新的頁面。該頁面可能是帶有已經填充某些數據的新表單的 HTML,也可能是確認頁面,或者是具有根據原來表單中輸入數據選擇的某些選項的頁面。當然,在伺服器上的腳本或程式處理和返回新表單時用戶必須等待。屏幕變成一片空白,等到伺服器返回數據後再重新繪製。這就是交互性差的原因,用戶得不到立即反饋,因此感覺不同於桌面應用程式。
Ajax 基本上就是把 JavaScript 技術和 XMLHttpRequest 對象放在 Web 表單和伺服器之間。當用戶填寫表單時,數據發送給一些 JavaScript 代碼而不是 直接發送給伺服器。相反,JavaScript 代碼捕獲表單數據並向伺服器發送請求。同時用戶屏幕上的表單也不會閃爍、消失或延遲。換句話說,JavaScript 代碼在幕後發送請求,用戶甚至不知道請求的發出。更好的是,請求是非同步發送的,就是說 JavaScript 代碼(和用戶)不用等待伺服器的響應。因此用戶可以繼續輸入數據、滾動屏幕和使用應用程式。
然後,伺服器將數據返回 JavaScript 代碼(仍然在 Web 表單中),後者決定如何處理這些數據。它可以迅速更新表單數據,讓人感覺應用程式是立即完成的,表單沒有提交或刷新而用戶得到了新數據。JavaScript 代碼甚至可以對收到的數據執行某種計算,再發送另一個請求,完全不需要用戶干預!這就是 XMLHttpRequest 的強大之處。它可以根據需要自行與伺服器進行交互,用戶甚至可以完全不知道幕後發生的一切。結果就是類似於桌面應用程式的動態、快速響應、高交互性的體驗,但是背後又擁有互聯網的全部強大力量。
加入一些 JavaScript
得到 XMLHttpRequest 的句柄後,其他的 JavaScript 代碼就非常簡單了。事實上,我們將使用 JavaScript 代碼完成非常基本的任務:
·獲取表單數據:JavaScript 代碼很容易從 HTML 表單中抽取數據併發送到伺服器。
·修改表單上的數據:更新表單也很簡單,從設置欄位值到迅速替換圖像。
·解析 HTML 和 XML:使用 JavaScript 代碼操縱 DOM(請參閱 下一節),處理 HTML 表單伺服器返回的 XML 數據的結構。
對於前兩點,需要非常熟悉 getElementById() 方法,如 清單 2 所示。
清單 2. 用 JavaScript 代碼捕獲和設置欄位值
// Get the value of the "phone" field and stuff it in a variable called phone
var phone = document.getElementById("phone").value;
// Set some values on a form using an array called response
document.getElementById("order").value = response[0];
document.getElementById("address").value = response[1];
這裡沒有特別需要註意的地方,真是好極了!您應該認識到這裡並沒有非常複雜的東西。只要掌握了 XMLHttpRequest,Ajax 應用程式的其他部分就是如 清單 2 所示的簡單 JavaScript 代碼了,混合有少量的 HTML。同時,還要用一點兒 DOM,我們就來看看吧。
以 DOM 結束
最後還有 DOM,即文檔對象模型。可能對有些讀者來說 DOM 有點兒令人生畏,HTML 設計者很少使用它,即使 JavaScript 程式員也不大用到它,除非要完成某項高端編程任務。大量使用 DOM 的是 複雜的 Java 和 C/C++ 程式,這可能就是 DOM 被認為難以學習的原因。
幸運的是,在 JavaScript 技術中使用 DOM 很容易,也非常直觀。現在,按照常規也許應該說明如何使用 DOM,或者至少要給出一些示例代碼,但這樣做也可能誤導您。即使不理會 DOM,仍然能深入地探討 Ajax,這也是我準備採用的方法。以後的文章將再次討論 DOM,現在只要知道可能需要 DOM 就可以了。當需要在 JavaScript 代碼和伺服器之間傳遞 XML 和改變 HTML 表單的時候,我們再深入研究 DOM。沒有它也能做一些有趣的工作,因此現在就把 DOM 放到一邊吧。
獲取 Request 對象
有了上面的基礎知識後,我們來看看一些具體的例子。XMLHttpRequest 是 Ajax 應用程式的核心,而且對很多讀者來說可能還比較陌生,我們就從這裡開始吧。從 清單 1 可以看出,創建和使用這個對象非常簡單,不是嗎?等一等。
還記得幾年前的那些討厭的瀏覽器戰爭嗎?沒有一樣東西在不同的瀏覽器上得到同樣的結果。不管您是否相信,這些戰爭仍然在繼續,雖然規模較小。但令人奇怪的是,XMLHttpRequest 成了這場戰爭的犧牲品之一。因此獲得 XMLHttpRequest 對象可能需要採用不同的方法。下麵我將詳細地進行解釋。
使用 Microsoft 瀏覽器
Microsoft 瀏覽器 Internet Explorer 使用 MSXML 解析器處理 XML(可以通過 參考資料 進一步瞭解 MSXML)。因此如果編寫的 Ajax 應用程式要和 Internet Explorer 打交道,那麼必須用一種特殊的方式創建對象。
但並不是這麼簡單。根據 Internet Explorer 中安裝的 JavaScript 技術版本不同,MSXML 實際上有兩種不同的版本,因此必須對這兩種情況分別編寫代碼。請參閱 清單 3,其中的代碼在 Microsoft 瀏覽器上創建了一個 XMLHttpRequest。
清單 3. 在 Microsoft 瀏覽器上創建 XMLHttpRequest 對象
var xmlHttp = false;
try {
xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e2) {
xmlHttp = false;
}
}
您對這些代碼可能還不完全理解,但沒有關係。當本系列文章結束的時候,您將對 JavaScript 編程、錯誤處理、條件編譯等有更深的瞭解。現在只要牢牢記住其中的兩行代碼:
xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
和
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");。
這兩行代碼基本上就是嘗試使用一個版本的 MSXML 創建對象,如果失敗則使用另一個版本創建該對象。不錯吧?如果都不成功,則將 xmlHttp 變數設為 false,告訴您的代碼出現了問題。如果出現這種情況,可能是因為安裝了非 Microsoft 瀏覽器,需要使用不同的代碼。
處理 Mozilla 和非 Microsoft 瀏覽器
如果選擇的瀏覽器不是 Internet Explorer,或者為非 Microsoft 瀏覽器編寫代碼,就需要使用不同的代碼。事實上就是 清單 1 所示的一行簡單代碼:
var xmlHttp = new XMLHttpRequest object;。
這行簡單得多的代碼在 Mozilla、Firefox、Safari、Opera 以及基本上所有以任何形式或方式支持 Ajax 的非 Microsoft 瀏覽器中,創建了 XMLHttpRequest 對象。
結合起來
關鍵是要支持所有 瀏覽器。誰願意編寫一個只能用於 Internet Explorer 或者非 Microsoft 瀏覽器的應用程式呢?或者更糟,要編寫一個應用程式兩次?當然不!因此代碼要同時支持 Internet Explorer 和非 Microsoft 瀏覽器。清單 4 顯示了這樣的代碼。
清單 4. 以支持多種瀏覽器的方式創建 XMLHttpRequest 對象
/* Create a new XMLHttpRequest object to talk to the Web server */
var xmlHttp = false;
/*@cc_on @*/
/*@if (@_jscript_version >= 5)
try {
xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e2) {
xmlHttp = false;
}
}
@end @*/
if (!xmlHttp && typeof XMLHttpRequest != 'undefined') {
xmlHttp = new XMLHttpRequest();
}
現在先不管那些註釋掉的奇怪符號,如 @cc_on,這是特殊的 JavaScript 編譯器命令,將在下一期針對 XMLHttpRequest 的文章中詳細討論。這段代碼的核心分為三步:
1、建立一個變數 xmlHttp 來引用即將創建的 XMLHttpRequest 對象。
2、嘗試在 Microsoft 瀏覽器中創建該對象:
1)嘗試使用 Msxml2.XMLHTTP 對象創建它。
2)如果失敗,再嘗試 Microsoft.XMLHTTP 對象。
2、如果仍然沒有建立 xmlHttp,則以非 Microsoft 的方式創建該對象。
最後,xmlHttp 應該引用一個有效的 XMLHttpRequest 對象,無論運行什麼樣的瀏覽器。
關於安全性的一點說明
安全性如何呢?現在瀏覽器允許用戶提高他們的安全等級,關閉 JavaScript 技術,禁用瀏覽器中的任何選項。在這種情況下,代碼無論如何都不會工作。此時必須適當地處理問題,這需要單獨的一篇文章來討論,要放到以後了(這個系列夠長了吧?不用擔心,讀完之前也許您就掌握了)。現在要編寫一段健壯但不夠完美的代碼,對於掌握 Ajax 來說就很好了。以後我們還將討論更多的細節。
Ajax 世界中的請求/響應
現在我們介紹了 Ajax,對 XMLHttpRequest 對象以及如何創建它也有了基本的瞭解。如果閱讀得很仔細,您可能已經知道與伺服器上的 Web 應用程式打交道的是 JavaScript 技術,而不是直接提交給那個應用程式的 HTML 表單。
還缺少什麼呢?到底如何使用 XMLHttpRequest。因為這段代碼非常重要,您編寫的每個 Ajax 應用程式都要以某種形式使用它,先看看 Ajax 的基本請求/響應模型是什麼樣吧。
發出請求
您已經有了一個嶄新的 XMLHttpRequest 對象,現在讓它乾點活兒吧。首先需要一個 Web 頁面能夠調用的 JavaScript 方法(比如當用戶輸入文本或者從菜單中選擇一項時)。接下來就是在所有 Ajax 應用程式中基本都雷同的流程:
1、從 Web 表單中獲取需要的數據。
2、建立要連接的 URL。
3、打開到伺服器的連接。
4、設置伺服器在完成後要運行的函數。
5、發送請求。
清單 5 中的示例 Ajax 方法就是按照這個順序組織的:
清單 5. 發出 Ajax 請求
function callServer() {
// Get the city and state from the web form
var city = document.getElementById("city").value;
var state = document.getElementById("state").value;
// Only go on if there are values for both fields
if ((city == null) || (city == "")) return;
if ((state == null) || (state == "")) return;
// Build the URL to connect to
var url = "/scripts/getZipCode.php?city=" + escape(city) + "&state=" + escape(state);
// Open a connection to the server
xmlHttp.open("GET", url, true);
// Setup a function for the server to run when it's done
xmlHttp.onreadystatechange = updatePage;
// Send the request
xmlHttp.send(null);
}
其中大部分代碼意義都很明確。開始的代碼使用基本 JavaScript 代碼獲取幾個表單欄位的值。然後設置一個 PHP 腳本作為鏈接的目標。要註意腳本 URL 的指定方式,city 和 state(來自表單)使用簡單的 GET 參數附加在 URL 之後。
然後打開一個連接,這是您第一次看到使用 XMLHttpRequest。其中指定了連接方法(GET)和要連接的 URL。最後一個參數如果設為 true,那麼將請求一個非同步連接(這就是 Ajax 的由來)。如果使用 false,那麼代碼發出請求後將等待伺服器返回的響應。如果設為 true,當伺服器在後臺處理請求的時候用戶仍然可以使用表單(甚至調用其他 JavaScript 方法)。
xmlHttp(要記住,這是 XMLHttpRequest 對象實例)的 onreadystatechange 屬性可以告訴伺服器在運行完成 後(可能要用五分鐘或者五個小時)做什麼。因為代碼沒有等待伺服器,必須讓伺服器知道怎麼做以便您能作出響應。在這個示例中,如果伺服器處理完了請求,一個特殊的名為 updatePage() 的方法將被觸發。
最後,使用值 null 調用 send()。因為已經在請求 URL 中添加了要發送給伺服器的數據(city 和 state),所以請求中不需要發送任何數據。這樣就發出了請求,伺服器按照您的要求工作。
如果沒有發現任何新鮮的東西,您應該體會到這是多麼簡單明瞭!除了牢牢記住 Ajax 的非同步特性外,這些內容都相當簡單。應該感激 Ajax 使您能夠專心編寫漂亮的應用程式和界面,而不用擔心複雜的 HTTP 請求/響應代碼。
清單 5 中的代碼說明瞭 Ajax 的易用性。數據是簡單的文本,可以作為請求 URL 的一部分。用 GET 而不是更複雜的 POST 發送請求。沒有 XML 和要添加的內容頭部,請求體中沒有要發送的數據;換句話說,這就是 Ajax 的烏托邦。
不用擔心,隨著本系列文章的展開,事情會變得越來越複雜。您將看到如何發送 POST 請求、如何設置請求頭部和內容類型、如何在消息中編碼 XML、如何增加請求的安全性,可以做的工作還有很多!暫時先不用管那些難點,掌握好基本的東西就行了,很快我們就會建立一整套的 Ajax 工具庫。
處理響應
現在要面對伺服器的響應了。現在只要知道兩點:
·什麼也不要做,直到 xmlHttp.readyState 屬性的值等於 4。
·伺服器將把響應填充到 xmlHttp.responseText 屬性中。
其中的第一點,即就緒狀態,將在下一篇文章中詳細討論,您將進一步瞭解 HTTP 請求的階段,可能比您設想的還多。現在只要檢查一個特定的值(4)就可以了(下一期文章中還有更多的值要介紹)。第二點,使用 xmlHttp.responseText 屬性獲得伺服器的響應,這很簡單。清單 6 中的示例方法可供伺服器根據 清單 5 中發送的數據調用。
清單 6. 處理伺服器響應
function updatePage() {
if (xmlHttp.readyState == 4) {
var response = xmlHttp.responseText;
document.getElementById("zipCode").value = response;
}
}
這些代碼同樣既不難也不複雜。它等待伺服器調用,如果是就緒狀態,則使用伺服器返回的值(這裡是用戶輸入的城市和州的 ZIP 編碼)設置另一個表單欄位的值。於是包含 ZIP 編碼的 zipCode 欄位突然出現了,而用戶沒有按任何按鈕!這就是前面所說的桌面應用程式的感覺。快速響應、動態感受等等,這些都只因為有了小小的一段 Ajax 代碼。
細心的讀者可能註意到 zipCode 是一個普通的文本欄位。一旦伺服器返回 ZIP 編碼,updatePage() 方法就用城市/州的 ZIP 編碼設置那個欄位的值,用戶就可以改寫該值。這樣做有兩個原因:保持例子簡單,說明有時候可能希望 用戶能夠修改伺服器返回的數據。要記住這兩點,它們對於好的用戶界面設計來說很重要。
連接 Web 表單
還有什麼呢?實際上沒有多少了。一個 JavaScript 方法捕捉用戶輸入表單的信息並將其發送到伺服器,另一個 JavaScript 方法監聽和處理響應,併在響應返回時設置欄位的值。所有這些實際上都依賴於調用 第一個 JavaScript 方法,它啟動了整個過程。最明顯的辦法是在 HTML 表單中增加一個按鈕,但這是 2001 年的辦法,您不這樣認為嗎?還是像 清單 7 這樣利用 JavaScript 技術吧。
清單 7. 啟動一個 Ajax 過程
<form>
<p>City: <input type="text" name="city" id="city" size="25"
onChange="callServer();" /></p>
<p>State: <input type="text" name="state" id="state" size="25"
onChange="callServer();" /></p>
<p>Zip Code: <input type="text" name="zipCode" id="city" size="5" /></p>
</form>
如果感覺這像是一段相當普通的代碼,那就對了,正是如此!當用戶在 city 或 state 欄位中輸入新的值時,callServer() 方法就被觸發,於是 Ajax 開始運行了。有點兒明白怎麼回事了吧?好,就是如此!
結束語
現在您可能已經準備開始編寫第一個 Ajax 應用程式了,至少也希望認真讀一下 參考資料 中的那些文章了吧?但可以首先從這些應用程式如何工作的基本概念開始,對 XMLHttpRequest 對象有基本的瞭解。在下一期文章中,您將掌握這個對象,學會如何處理 JavaScript 和伺服器的通信、如何使用 HTML 表單以及如何獲得 DOM 句柄。
現在先花點兒時間考慮考慮 Ajax 應用程式有多麼強大。設想一下,當單擊按鈕、輸入一個欄位、從組合框中選擇一個選項或者用滑鼠在屏幕上拖動時,Web 表單能夠立刻作出響應會是什麼情形。想一想非同步 究竟意味著什麼,想一想 JavaScript 代碼運行而且不等待 伺服器對它的請求作出響應。會遇到什麼樣的問題?會進入什麼樣的領域?考慮到這種新的方法,編程的時候應如何改變表單的設計?
如果在這些問題上花一點兒時間,與簡單地剪切/粘貼某些代碼到您根本不理解的應用程式中相比,收益會更多。在下一期文章中,我們將把這些概念付諸實踐,詳細介紹使應用程式按照這種方式工作所需要的代碼。因此,現在先享受一下 Ajax 所帶來的可能性吧。
第 2 頁 使用 JavaScript 和 Ajax 發出非同步請求
多數 Web 應用程式都使用請求/響應模型從伺服器上獲得完整的 HTML 頁面。常常是點擊一個按鈕,等待伺服器響應,再點擊另一個按鈕,然後再等待,這樣一個反覆的過程。有了 Ajax 和 XMLHttpRequest 對象,就可以使用不必讓用戶等待伺服器響應的請求/響應模型了。本文中,Brett McLaughlin 介紹瞭如何創建能夠適應不同瀏覽器的 XMLHttpRequest 實例,建立和發送請求,並響應伺服器。
本系列的上一期文章(請參閱 參考資料 中的鏈接),我們介紹了 Ajax 應用程式,考察了推動 Ajax 應用程式的基本概念。其中的核心是很多您可能已經瞭解的技術:JavaScript、HTML 和 XHTML、一點動態 HTML 以及 DOM(文檔對象模型)。本文將放大其中的一點,把目光放到具體的 Ajax 細節上。
本文中,您將開始接觸最基本和基礎性的有關 Ajax 的全部對象和編程方法:XMLHttpRequest 對象。該對象實際上僅僅是一個跨越所有 Ajax 應用程式的公共線程,您可能已經預料到,只有徹底理解該對象才能充分發揮編程的潛力。事實上,有時您會發現,要正確地使用 XMLHttpRequest,顯然不能 使用 XMLHttpRequest。這到底是怎麼回事呢?
Web 2.0 一瞥
在深入研究代碼之前首先看看最近的觀點 —— 一定要十分清楚 Web 2.0 這個概念。聽到 Web 2.0 這個詞的時候,應該首先問一問 “Web 1.0 是什麼?” 雖然很少聽人提到 Web 1.0,實際上它指的就是具有完全不同的請求和響應模型的傳統 Web。比如,到 Amazon.com 網站上點擊一個按鈕或者輸入搜索項。就會對伺服器發送一個請求,然後響應再返回到瀏覽器。該請求不僅僅是圖書和書目列表,而是另一個完整的 HTML 頁面。因此當 Web 瀏覽器用新的 HTML 頁面重繪時,可能會看到閃爍或抖動。事實上,通過看到的每個新頁面可以清晰地看到請求和響應。
Web 2.0(在很大程度上)消除了這種看得見的往複交互。比如訪問 Google Maps 或 Flickr 這樣的站點(到這些支持 Web 2.0 和 Ajax 站點的鏈接請參閱 參考資料)。比如在 Google Maps 上,您可以拖動地圖,放大和縮小,只有很少的重繪操作。當然這裡仍然有請求和響應,只不過都藏到了幕後。作為用戶,體驗更加舒適,感覺很像桌面應用程式。這種新的感受和範型就是當有人提到 Web 2.0 時您所體會到的。
需要關心的是如何使這些新的交互成為可能。顯然,仍然需要發出請求和接收響應,但正是針對每次請求/響應交互的 HTML 重繪造成了緩慢、笨拙的 Web 交互的感受。因此很清楚,我們需要一種方法使發送的請求和接收的響應只 包含需要的數據而不是整個 HTML 頁面。惟一需要獲得整個新 HTML 頁面的時候就是希望用戶看到 新頁面的時候。
但多數交互都是在已有頁面上增加細節、修改主體文本或者覆蓋原有數據。這些情況下,Ajax 和 Web 2.0 方法允許在不 更新整個 HTML 頁面的情況下發送和接收數據。對於那些經常上網的人,這種能力可以讓您的應用程式感覺更快、響應更及時,讓他們不時地光顧您的網站。
XMLHttpRequest 簡介
要真正實現這種絢麗的奇跡,必須非常熟悉一個 JavaScript 對象,即 XMLHttpRequest。這個小小的對象實際上已經在幾種瀏覽器中存在一段時間了,它是本專欄今後幾個月中要介紹的 Web 2.0、Ajax 和大部分其他內容的核心。為了讓您快速地大體瞭解它,下麵給出將要用於該對象的很少的幾個 方法和屬性。
·open():建立到伺服器的新請求。
·send():向伺服器發送請求。
·abort():退出當前請求。
·readyState:提供當前 HTML 的就緒狀態。
·responseText:伺服器返回的請求響應文本。
如果不瞭解這些(或者其中的任何 一個),您也不用擔心,後面幾篇文章中我們將介紹每個方法和屬性。現在應該 瞭解的是,明確用 XMLHttpRequest 做什麼。要註意這些方法和屬性都與發送請求及處理響應有關。事實上,如果看到 XMLHttpRequest 的所有方法和屬性,就會發現它們都 與非常簡單的請求/響應模型有關。顯然,我們不會遇到特別新的 GUI 對象或者創建用戶交互的某種超極神秘的方法,我們將使用非常簡單的請求和非常簡單的響應。聽起來似乎沒有多少吸引力,但是用好該對象可以徹底改變您的應用程式。
簡單的 new
首先需要創建一個新變數並賦給它一個 XMLHttpRequest 對象實例。這在 JavaScript 中很簡單,只要對該對象名使用 new 關鍵字即可,如 清單 1 所示。
清單 1. 創建新的 XMLHttpRequest 對象
<script language="javascript" type="text/javascript">
var request = new XMLHttpRequest();
</script>
不難吧?記住,JavaScript 不要求指定變數類型,因此不需要像 清單 2 那樣做(在 Java 語言中可能需要這樣)。
清單 2. 創建 XMLHttpRequest 的 Java 偽代碼
XMLHttpRequest request = new XMLHttpRequest();
因此在 JavaScript 中用 var 創建一個變數,給它一個名字(如 “request”),然後賦給它一個新的 XMLHttpRequest 實例。此後就可以在函數中使用該對象了。
錯誤處理
在實際上各種事情都可能出錯,而上面的代碼沒有提供任何錯誤處理。較好的辦法是創建該對象,併在出現問題時優雅地退出。比如,任何較早的瀏覽器(不論您是否相信,仍然有人在使用老版本的 Netscape Navigator)都不支持 XMLHttpRequest,您需要讓這些用戶知道有些地方出了問題。清單 3 說明如何創建該對象,以便在出現問題的時候發出 JavaScript 警告。
清單 3. 創建具有錯誤處理能力的 XMLHttpRequest
<script language="javascript" type="text/javascript">
var request = false;
try {
request = new XMLHttpRequest();
} catch (failed) {
request = false;
}
if (!request)
alert("Error initializing XMLHttpRequest!");
</script>
一定要理解這些步驟:
1、創建一個新變數 request 並賦值 false。後面將使用 false 作為判定條件,它表示還沒有創建 XMLHttpRequest 對象。
2、增加 try/catch 塊:
1)嘗試創建 XMLHttpRequest 對象。
2)如果失敗(catch (failed))則保證 request 的值仍然為 false。
3、檢查 request 是否仍為 false(如果一切正常就不會是 false)。
4、如果出現問題(request 是 false)則使用 JavaScript 警告通知用戶出現了問題。
代碼非常簡單,對大多數 JavaScript 和 Web 開發人員來說,真正理解它要比讀寫代碼花更長的時間。現在已經得到了一段帶有錯誤檢查的 XMLHttpRequest 對象創建代碼,還可以告訴您哪兒出了問題。
應付 Microsoft
看起來似乎一切良好,至少在用 Internet Explorer 試驗這些代碼之前是這樣的。如果這樣試驗的話,就會看到 圖 1 所示的糟糕情形。
圖 1. Internet Explorer 報告錯誤
顯然有什麼地方不對勁,而 Internet Explorer 很難說是一種過時的瀏覽器,因為全世界有 70% 在使用 Internet Explorer。換句話說,如果不支持 Microsoft 和 Internet Explorer 就不會受到 Web 世界的歡迎!因此我們需要採用不同的方法處理 Microsoft 瀏覽器。
經驗證發現 Microsoft 支持 Ajax,但是其 XMLHttpRequest 版本有不同的稱呼。事實上,它將其稱為幾種 不同的東西。如果使用較新版本的 Internet Explorer,則需要使用對象 Msxml2.XMLHTTP,而較老版本的 Internet Explorer 則使用 Microsoft.XMLHTTP。我們需要支持這兩種對象類型(同時還要支持非 Microsoft 瀏覽器)。請看看 清單 4,它在前述代碼的基礎上增加了對 Microsoft 的支持。
Microsoft 參與了嗎?
關於 Ajax 和 Microsoft 對該領域不斷增長的興趣和參與已經有很多文章進行了介紹。事實上,據說 Microsoft 最新版本的 Internet Explorer —— version 7.0,將在 2006 年下半年推出 —— 將開始直接支持 XMLHttpRequest,讓您使用 new 關鍵字代替所有的 Msxml2.XMLHTTP 創建代碼。但不要太激動,仍然需要支持舊的瀏覽器,因此跨瀏覽器代碼不會很快消失。
清單 4. 增加對 Microsoft 瀏覽器的支持
<script language="javascript" type="text/javascript">
var request = false;
try {
request = new XMLHttpRequest();
} catch (trymicrosoft) {
try {
request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (othermicrosoft) {
try {
request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (failed) {
request = false;
}
}
}
if (!request)
alert("Error initializing XMLHttpRequest!");
</script>
很容易被這些花括弧迷住了眼睛,因此下麵分別介紹每一步:
1、創建一個新變數 request 並賦值 false。使用 false 作為判斷條件,它表示還沒有創建 XMLHttpRequest 對象。
2、增加 try/catch 塊:
1)嘗試創建 XMLHttpRequest 對象。
2)如果失敗(catch (trymicrosoft)):
1>嘗試使用較新版本的 Microsoft 瀏覽器創建 Microsoft 相容的對象(Msxml2.XMLHTTP)。
2> 如果失敗(catch (othermicrosoft))嘗試使用較老版本的 Microsoft 瀏覽器創建 Microsoft 相容的對象(Microsoft.XMLHTTP)。
2)如果失敗(catch (failed))則保證 request 的值仍然為 false。
3、檢查 request 是否仍然為 false(如果一切順利就不會是 false)。
4、如果出現問題(request 是 false)則使用 JavaScript 警告通知用戶出現了問題。
這樣修改代碼之後再使用 Internet Explorer 試驗,就應該看到已經創建的表單(沒有錯誤消息)。我實驗的結果如 圖 2 所示。
圖 2. Internet Explorer 正常工作
靜態與動態
再看一看清單 1、3 和 4,註意,所有這些代碼都直接嵌套在 script 標記中。像這種不放到方法或函數體中的 JavaScript 代碼稱為靜態 JavaScript。就是說代碼是在頁面顯示給用戶之前的某個時候運行。(雖然根據規範不能完全精確地 知道這些代碼何時運行對瀏覽器有什麼影響,但是可以保證這些代碼在用戶能夠與頁面交互之前運行。)這也是多數 Ajax 程式員創建 XMLHttpRequest 對象的一般方式。
就是說,也可以像 清單 5 那樣將這些代碼放在一個方法中。
清單 5. 將 XMLHttpRequest 創建代碼移動到方法中
<script language="javascript" type="text/javascript">
var request;
function createRequest() {
try {
request = new XMLHttpRequest();
} catch (trymicrosoft) {
try {
request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (othermicrosoft) {
try {
request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (failed) {
request = false;
}
}
}
if (!request)
alert("Error initializing XMLHttpRequest!");
}
</script>
如果按照這種方式編寫代碼,那麼在處理 Ajax 之前需要調用該方法。因此還需要 清單 6 這樣的代碼。
清單 6. 使用 XMLHttpRequest 的創建方法
<script language="javascript" type="text/javascript">
var request;
function createRequest() {
try {
request = new XMLHttpRequest();
} catch (trymicrosoft) {
try {
request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (othermicrosoft) {
try {
request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (failed) {
request = false;
}
}
}
if (!request)
alert("Error initializing XMLHttpRequest!");
}
function getCustomerInfo() {
createRequest();
// Do something with the request variable
}
</script>
此代碼惟一的問題是推遲了錯誤通知,這也是多數 Ajax 程式員不採用這一方法的原因。假設一個複雜的表單有 10 或 15 個欄位、選擇框等,當用戶在第 14 個欄位(按照表單順序從上到下)輸入文本時要激活某些 Ajax 代碼。這時候運行 getCustomerInfo() 嘗試創建一個 XMLHttpRequest 對象,但(對於本例來說)失敗了。然後向用戶顯示一條警告,明確地告訴他們不能使用該應用程式。但用戶已經花費了很多時間在表單中輸入數據!這是非常令人討厭的,而討厭顯然不會吸引用戶再次訪問您的網站。
如果使用靜態 JavaScript,用戶在點擊頁面的時候很快就會看到錯誤信息。這樣也很煩人,是不是?可能令用戶錯誤地認為您的 Web 應用程式不能在他的瀏覽器上運行。不過,當然要比他們花費了 10 分鐘輸入信息之後再顯示同樣的錯誤要好。因此,我建議編寫靜態的代碼,讓用戶儘可能早地發現問題。
用 XMLHttpRequest 發送請求
得到請求對象之後就可以進入請求/響應迴圈了。記住,XMLHttpRequest 惟一的目的是讓您發送請求和接收響應。其他一切都是 JavaScript、CSS 或頁面中其他代碼的工作:改變用戶界面、切換圖像、解釋伺服器返回的數據。準備好 XMLHttpRequest 之後,就可以向伺服器發送請求了。
歡迎使用沙箱
Ajax 採用一種沙箱安全模型。因此,Ajax 代碼(具體來說就是 XMLHttpRequest 對象)只能對所在的同一個域發送請求。以後的文章中將進一步介紹安全和 Ajax,現在只要知道在本地機器上運行的代碼只能對本地機器上的伺服器端腳本發送請求。如果讓 Ajax 代碼在 http://www.breakneckpizza.com/ 上運行,則必須 http://www.breakneck.com/ 中運行的腳本發送請求。
設置伺服器 URL
首先要確定連接的伺服器的 URL。這並不是 Ajax 的特殊要求,但仍然是建立連接所必需的,顯然現在您應該知道如何構造 URL 了。多數應用程式中都會結合一些靜態數據和用戶處理的表單中的數據來構造該 URL。比如,清單 7 中的 JavaScript 代碼獲取電話號碼欄位的值並用其構造 URL。
清單 7. 建立請求 URL
<script language="javascript" type="text/javascript">
var request = false;
try {
request = new XMLHttpRequest();
} catch (trymicrosoft) {
try {
request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (othermicrosoft) {
try {
request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (failed) {
request = false;
}
}
}
if (!request)
alert("Error initializing XMLHttpRequest!");
function getCustomerInfo() {
var phone = document.getElementById("phone").value;
var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
}
</script>
這裡沒有難懂的地方。首先,代碼創建了一個新變數 phone,並把 ID 為 “phone” 的表單欄位的值賦給它。清單 8 展示了這個表單的 XHTML,其中可以看到 phone 欄位及其 id 屬性。
清單 8. Break Neck Pizza 表單
<body>
<p><img src="breakneck-logo_4c.gif" alt="Break Neck Pizza" /></p>
<form action="POST">
<p>Enter your phone number:
<input type="text" size="14" name="phone" id="phone"
onChange="getCustomerInfo();" />
</p>
<p>Your order will be delivered to:</p>
<div id="address"></div>
<p>Type your order in here:</p>
<p><textarea name="order" rows="6" cols="50" id="order"></textarea></p>
<p><input type="submit" value="Order Pizza" id="submit" /></p>
</form>
</body>
還要註意,當用戶輸入電話號碼或者改變電話號碼時,將觸發 清單 8 所示的 getCustomerInfo() 方法。該方法取得電話號碼並構造存儲在 url 變數中的 URL 字元串。記住,由於 Ajax 代碼是沙箱型的,因而只能連接到同一個域,實際上 URL 中不需要功能變數名稱。該例中的腳本名為 /cgi-local/lookupCustomer.php。最後,電話號碼作為 GET 參數附加到該腳本中:"phone=" + escape(phone)。
如果以前沒用見過 escape() 方法,它用於轉義不能用明文正確發送的任何字元。比如,電話號碼中的空格將被轉換成字元 %20,從而能夠在 URL 中傳遞這些字元。
可以根據需要添加任意多個參數。比如,如果需要增加另一個參數,只需要將其附加到 URL 中並用 “與”(&)字元分開 [第一個參數用問號(?)和腳本名分開]。
打開請求
有了要連接的 URL 後就可以配置請求了。可以用 XMLHttpRequest 對象的 open() 方法來完成。該方法有五個參數:
request-type:發送請求的類型。典型的值是 GET 或 POST,但也可以發送 HEAD 請求。
url:要連接的 URL。
asynch:如果希望使用非同步連接則為 true,否則為 false。該參數是可選的,預設為 true。
username:如果需要身份驗證,則可以在此指定用戶名。該可選參數沒有預設值。 password:如果需要身份驗證,則可以在此指定口令。該可選參數沒有預設值。
open() 是打開嗎?
Internet 開發人員對 open() 方法到底做什麼沒有達成一致。但它實際上並不是 打開一個請求。如果監控 XHTML/Ajax 頁面及其連接腳本之間的網路和數據傳遞,當調用 open() 方法時將看不到任何通信。不清楚為何選用了這個名字,但顯然不是一個好的選擇。
通常使用其中的前三個參數。事實上,即使需要非同步連接,也應該指定第三個參數為 “true”。這是預設值,但堅持明確指定請求是非同步的還是同步的更容易理解。
將這些結合起來,通常會得到 清單 9 所示的一行代碼。
清單 9. 打開請求
function getCustomerInfo() {
var phone = document.getElementById("phone").value;
var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
request.open("GET", url, true);
}
一旦設置好了 URL,其他就簡單了。多數請求使用 GET 就夠了(後面的文章中將看到需要使用 POST 的情況),再加上 URL,這就是使用 open() 方法需要的全部內容了。
挑戰非同步性
本系列的後面一篇文章中,我將用很多時間編寫和使用非同步代碼,但是您應該明白為什麼 open() 的最後一個參數這麼重要。在一般的請求/響應模型中,比如 Web 1.0,客戶機(瀏覽器或者本地機器上運行的代碼)向伺服器發出請求。該請求是同步的,換句話說,客戶機等待伺服器的響應。當客戶機等待的時候,至少會用某種形式通知您在等待:
·沙漏(特別是 Windows 上)。
·旋轉的皮球(通常在 Mac 機器上)。
·應用程式基本上凍結了,然後過一段時間游標變化了。
這正是 Web 應用程式讓人感到笨拙或緩慢的原因 —— 缺乏真正的交互性。按下按鈕時,應用程式實際上變得不能使用,直到剛剛觸發的請求得到響應。如果請求需要大量伺服器處理,那麼等待的時間可能很長(至少在這個多處理器、DSL 沒有等待的世界中是如此)。
而非同步請求不 等待伺服器響應。發送請求後應用程式繼續運行。用戶仍然可以在 Web 表單中輸入數據,甚至離開表單。沒有旋轉的皮球或者沙漏,應用程式也沒有明顯的凍結。伺服器悄悄地響應請求,完成後告訴原來的請求者工作已經結束(具體的辦法很快就會看到)。結果是,應用程式感覺不 那麼遲鈍或者緩慢,而是響應迅速、交互性強,感覺快多了。這僅僅是 Web 2.0 的一部分,但它是很重要的一部分。所有老套的 GUI 組件和 Web 設計範型都不能剋服緩慢、同步的請求/響應模型。
發送請求
一旦用 open() 配置好之後,就可以發送請求了。幸運的是,發送請求的方法的名稱要比 open() 適當,它就是 send()。
send() 只有一個參數,就是要發送的內容。但是在考慮這個方法之前,回想一下前面已經通過 URL 本身發送過數據了:
var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
雖然可以使用 send() 發送數據,但也能通過 URL 本身發送數據。事實上,GET 請求(在典型的 Ajax 應用中大約占 80%)中,用 URL 發送數據要容易得多。如果需要發送安全信息或 XML,可能要考慮使用 send() 發送內容(本系列的後續文章中將討論安全數據和 XML 消息)。如果不需要通過 send() 傳遞數據,則只要傳遞 null 作為該方法的參數即可。因此您會發現在本文中的例子中只需要這樣發送請求(參見 清單 10)。
清單 10. 發送請求
function getCustomerInfo() {
var phone = document.getElementById("phone").value;
var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
request.open("GET", url, true);
request.send(null);
}
指定回調方法
現在我們所做的只有很少一點是新的、革命性的或非同步的。必須承認,open() 方法中 “true” 這個小小的關鍵字建立了非同步請求。但是除此之外,這些代碼與用 Java servlet 及 JSP、PHP 或 Perl 編程沒有什麼兩樣。那麼 Ajax 和 Web 2.0 最大的秘密是什麼呢?秘密就在於 XMLHttpRequest 的一個簡單屬性 onreadystatechange。
首先一定要理解這些代碼中的流程(如果需要請回顧 清單 10)。建立其請求然後發出請求。此外,因為是非同步請求,所以 JavaScript 方法(例子中的 getCustomerInfo())不會等待伺服器。因此代碼將繼續執行,就是說,將退出該方法而把控制返回給表單。用戶可以繼續輸入信息,應用程式不會等待伺服器。
這就提出了一個有趣的問題:伺服器完成了請求之後會發生什麼?答案是什麼也不發生,至少對現在的代碼而言如此!顯然這樣不行,因此伺服器在完成通過 XMLHttpRequest 發送給它的請求處理之後需要某種指示說明怎麼做。
在 JavaScript 中引用函數:
JavaScript 是一種弱類型的語言,可以用變數引用任何東西。因此如果聲明瞭一個函數 updatePage(),JavaScript 也將該函數名看作是一個變數。換句話說,可用變數名 updatePage 在代碼中引用函數。
清單 11. 設置回調方法
function getCustomerInfo() {
var phone = document.getElementById("phone").value;
var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
request.open("GET", url, true);
request.onreadystatechange = updatePage;
request.send(null);
}
需要特別註意的是該屬性在代碼中設置的位置 —— 它是在調用 send() 之前 設置的。發送請求之前必須設置該屬性,這樣伺服器在回答完成請求之後才能查看該屬性。現在剩下的就只有編寫 updatePage() 方法了,這是本文最後一節要討論的重點。
處理伺服器響應
發送請求,用戶高興地使用 Web 表單(同時伺服器在處理請求),而現在伺服器完成了請求處理。伺服器查看 onreadystatechange 屬性確定要調用的方法。除此以外,可以將您的應用程式看作其他應用程式一樣,無論是否非同步。換句話說,不一定要採取特殊的動作編寫響應伺服器的方法,只需要改變表單,讓用戶訪問另一個 URL 或者做響應伺服器需要的任何事情。這一節我們重點討論對伺服器的響應和一種典型的動作 —— 即時改變用戶看到的表單中的一部分。
回調和 Ajax
現在我們已經看到如何告訴伺服器完成後應該做什麼:將 XMLHttpRequest 對象的 onreadystatechange 屬性設置為要運行的函數名。這樣,當伺服器處理完請求後就會自動調用該函數。也不需要擔心該函數的任何參數。我們從一個簡單的方法開始,如 清單 12 所示。
清單 12. 回調方法的代碼
<script language="javascript" type="text/javascript">
var request = false;
try {
request = new XMLHttpRequest();
} catch (trymicrosoft) {
try {
request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (othermicrosoft) {
try {
request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (failed) {
request = false;
}
}
}
if (!request)
alert("Error initializing XMLHttpRequest!");
function getCustomerInfo() {
var phone = document.getElementById("phone").value;
var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
request.open("GET", url, true);
request.onreadystatechange = updatePage;
request.send(null);
}
function updatePage() {
alert("Server is done!");
}
</script>
它僅僅發出一些簡單的警告,告訴您伺服器什麼時候完成了任務。在自己的網頁中試驗這些代碼,然後在瀏覽器中打開(如果希望查看該例中的 XHTML,請參閱 清單 8)。輸入電話號碼然後離開該欄位,將看到一個彈出的警告視窗(如 圖 3 所示),但是點擊 OK 又出現了……
圖 3. 彈出警告的 Ajax 代碼
根據瀏覽器的不同,在表單停止彈出警告之前會看到兩次、三次甚至四次警告。這是怎麼回事呢?原來我們還沒有考慮 HTTP 就緒狀態,這是請求/響應迴圈中的一個重要部分。
HTTP 就緒狀態
前面提到,伺服器在完成請求之後會在 XMLHttpRequest 的 onreadystatechange 屬性中查找要調用的方法。這是真的,但還不完整。事實上,每當 HTTP 就緒狀態改變時它都會調用該方法。這意味著什麼呢?首先必須理解 HTTP 就緒狀態。
HTTP 就緒狀態表示請求的狀態或情形。它用於確定該請求是否已經開始、是否得到了響應或者請求/響應模型是否已經完成。它還可以幫助確定讀取伺服器提供的響應文本或數據是否安全。在 Ajax 應用程式中需要瞭解五種就緒狀態:
·0:請求沒有發出(在調用 open() 之前)。
·1:請求已經建立但還沒有發出(調用 send() 之前)。
·2:請求已經發出正在處理之中(這裡通常可以從響應得到內容頭部)。
·3:請求已經處理,響應中通常有部分數據可用,但是伺服器還沒有完成響應。
·4:響應已完成,可以訪問伺服器響應並使用它。
與大多數跨瀏覽器問題一樣,這些就緒狀態的使用也不盡一致。您也許期望任務就緒狀態從 0 到 1、2、3 再到 4,但實際上很少是這種情況。一些瀏覽器從不報告 0 或 1 而直接從 2 開始,然後是 3 和 4。其他瀏覽器則報告所有的狀態。還有一些則多次報告就緒狀態 1。在上一節中看到,伺服器多次調用 updatePage(),每次調用都會彈出警告框 —— 可能和預期的不同!
對於 Ajax 編程,需要直接處理的惟一狀態就是就緒狀態 4,它表示伺服器響應已經完成,可以安全地使用響應數據了。基於此,回調方法中的第一行應該如 清單 13 所示。
清單 13. 檢查就緒狀態
function updatePage() {
if (request.readyState == 4)
alert("Server is done!");
}
修改後就可以保證伺服器的處理已經完成。嘗試運行新版本的 Ajax 代碼,現在就會看到與預期的一樣,只顯示一次警告信息了。
HTTP 狀態碼
雖然 清單 13 中的代碼看起來似乎不錯,但是還有一個問題 —— 如果伺服器響應請求並完成了處理但是報告了一個錯誤怎麼辦?要知道,伺服器端代碼應該明白它是由 Ajax、JSP、普通 HTML 表單或其他類型的代碼調用的,但只能使用傳統的 Web 專用方法報告信息。而在 Web 世界中,HTTP 代碼可以處理請求中可能發生的各種問題。
比方說,您肯定遇到過輸入了錯誤的 URL 請求而得到 404 錯誤碼的情形,它表示該頁面不存在。這僅僅是 HTTP 請求能夠收到的眾多錯誤碼中的一種(完整的狀態碼列表請參閱 參考資料 中的鏈接)。表示所訪問數據受到保護或者禁止訪問的 403 和 401 也很常見。無論哪種情況,這些錯誤碼都是從完成的響應 得到的。換句話說,伺服器履行了請求(即 HTTP 就緒狀態是 4)但是沒有返回客戶機預期的數據。
因此除了就緒狀態外,還需要檢查 HTTP 狀態。我們期望的狀態碼是 200,它表示一切順利。如果就緒狀態是 4 而且狀態碼是 200,就可以處理伺服器的數據了,而且這些數據應該就是要求的數據(而不是錯誤或者其他有問題的信息)。因此還要在回調方法中增加狀態檢查,如 清單 14 所示。
清單 14. 檢查 HTTP 狀態碼
function updatePage() {
if (request.readyState == 4)
if (request.status == 200)
alert("Server is done!");
}
為了增加更健壯的錯誤處理並儘量避免過於複雜,可以增加一兩個狀態碼檢查,請看一看 清單 15 中修改後的 updatePage() 版本。
清單 15. 增加一點錯誤檢查
function updatePage() {
if (request.readyState == 4)
if (request.status == 200)
alert("Server is done!");
else if (request.status == 404)
alert("Request URL does not exist");
else
alert("Error: status code is " + request.status);
}
現在將 getCustomerInfo() 中的 URL 改為不存在的 URL 看看會發生什麼。應該會看到警告信息說明要求的 URL 不存在 —— 好極了!很難處理所有的錯誤條件,但是這一小小的改變能夠涵蓋典型 Web 應用程式中 80% 的問題。
讀取響應文本
現在可以確保請求已經處理完成(通過就緒狀態),伺服器給出了正常的響應(通過狀態碼),最後我們可以處理伺服器返回的數據了。返回的數據保存在 XMLHttpRequest 對象的 responseText 屬性中。
關於 responseText 中的文本內容,比如格式和長度,有意保持含糊。這樣伺服器就可以將文本設置成任何內容。比方說,一種腳本可能返回逗號分隔的值,另一種則使用管道符(即 | 字元)分隔的值,還有一種則返回長文本字元串。何去何從由伺服器決定。
在本文使用的例子中,伺服器返回客戶的上一個訂單和客戶地址,中間用管道符分開。然後使用訂單和地址設置表單中的元素值,清單 16 給出了更新顯示內容的代碼。
清單 16. 處理伺服器響應
function updatePage() {
if (request.readyState == 4) {
if (request.status == 200) {
var response = request.responseText.split("|");
document.getElementById("order").value = response[0];
document.getElementById("address").innerHTML =
response[1].replace(/\n/g, "");
} else
alert("status is " + request.status);
}
}
首先,得到 responseText 並使用 JavaScript split() 方法從管道符分開。得到的數組放到 response 中。數組中的第一個值 —— 上一個訂單 —— 用 response[0] 訪問,被設置為 ID 為 “order” 的欄位的值。第二個值 response[1],即客戶地址,則需要更多一點處理。因為地址中的行用一般的行分隔符(“\n”字元)分隔,代碼中需要用 XHTML 風格的行分隔符 <br /> 來代替。替換過程使用 replace() 函數和正則表達式完成。最後,修改後的文本作為 HTML 表單 div 中的內部 HTML。結果就是表單突然用客戶信息更新了,如圖 4 所示。
圖 4. 收到客戶數據後的 Break Neck 表單
結束本文之前,我還要介紹 XMLHttpRequest 的另一個重要屬性 responseXML。如果伺服器選擇使用 XML 響應則該屬性包含(也許您已經猜到)XML 響應。處理 XML 響應和處理普通文本有很大不同,涉及到解析、文檔對象模型(DOM)和其他一些問題。後面的文章中將進一步介紹 XML。但是因為 responseXML 通常和 responseText 一起討論,這裡有必要提一提。對於很多簡單的 Ajax 應用程式 responseText 就夠了,但是您很快就會看到通過 Ajax 應用程式也能很好地處理 XML。
結束語
您可能對 XMLHttpRequest 感到有點厭倦了,我很少看到一整篇文章討論一個對象,特別是這種簡單的對象。但是您將在使用 Ajax 編寫的每個頁面和應用程式中反覆使用該對象。坦白地說,關於 XMLHttpRequest 還真有一些可說的內容。下一期文章中將介紹如何在請求中使用 POST 及 GET,來設置請求中的內容頭部和從伺服器響應讀取內容頭部,理解如何在請求/響應模型中編碼請求和處理 XML。
再往後我們將介紹常見 Ajax 工具箱。這些工具箱實際上隱藏了本文所述的很多