掌握 Ajax,第 1 部分: Ajax 簡介
Brett McLaughlin , 作家,編輯, O'Reilly and Associates
2006 年 1 月 04 日
五年前,如果不知道 XML,您就是一隻無人重視的醜小鴨。十八個月前,Ruby 成了關注的中心,不知道 Ruby 的程序員只能坐冷板凳了。今天,如果想跟上最新的技術時尚,那您的目標就是 Ajax。
但是,Ajax 不僅僅是一種時尚,它是一種構建網站的強大方法,而且不像學習一種全新的語言那樣困難。
但在詳細探討 Ajax 是什麼之前,先讓我們花幾分鐘瞭解 Ajax 做什麼。目前,編寫應用程序時有兩種基本的選擇:
兩者是類似的,桌面應用程序通常以 CD 爲介質(有時候可從網站下載)並完全安裝到您的計算機上。桌面應用程序可能使用互聯網下載更新,但運行這些應用程序的代碼在桌面計算機上。Web 應用程序運行在某處的 Web 服務器上 —— 毫不奇怪,要通過 Web 瀏覽器訪問這種應用程序。
不過,比這些應用程序的運行代碼放在何處更重要的是,應用程序如何運轉以及如何與其進行交互。桌面應用程序一般很快(就在您的計算機上運行,不用等待互聯網連接),具有漂亮的用戶界面(通常和操作系統有關)和非凡的動態性。可以單擊、選擇、輸入、打開菜單和子菜單、到處巡遊,基本上不需要等待。
另一方面,Web 應用程序是最新的潮流,它們提供了在桌面上不能實現的服務(比如 Amazon.com 和 eBay)。但是,伴隨着 Web 的強大而出現的是等待,等待服務器響應,等待屏幕刷新,等待請求返回和生成新的頁面。
顯然這樣說過於簡略了,但基本的概念就是如此。您可能已經猜到,Ajax 嘗試建立桌面應用程序的功能和交互性,與不斷更新的 Web 應用程序之間的橋樑。可以使用像桌面應用程序中常見的動態用戶界面和漂亮的控件,不過是在 Web 應用程序中。
還等什麼呢?我們來看看 Ajax 如何將笨拙的 Web 界面轉化成能迅速響應的 Ajax 應用程序吧。
在談到 Ajax 時,實際上涉及到多種技術,要靈活地運用它必須深入瞭解這些不同的技術(本系列的頭幾篇文章將分別討論這些技術)。好消息是您可能已經非常熟悉其中的大部分技術,更好的是這些技術都很容易學習,並不像完整的編程語言(如 Java 或 Ruby)那樣困難。
順便說一下,Ajax 是 Asynchronous JavaScript and XML(以及 DHTML 等)的縮寫。這個短語是 Adaptive Path 的 Jesse James Garrett 發明的,按照 Jesse 的解釋,這不是個首字母縮寫詞。
|
|
下面是 Ajax 應用程序所用到的基本技術:
- HTML 用於建立 Web 表單並確定應用程序其他部分使用的字段。
- JavaScript 代碼是運行 Ajax 應用程序的核心代碼,幫助改進與服務器應用程序的通信。
- DHTML 或 Dynamic HTML,用於動態更新表單。我們將使用
div
、span
和其他動態 HTML 元素來標記 HTML。
- 文檔對象模型 DOM 用於(通過 JavaScript 代碼)處理 HTML 結構和(某些情況下)服務器返回的 XML。
我們來進一步分析這些技術的職責。以後的文章中我將深入討論這些技術,目前只要熟悉這些組件和技術就可以了。對這些代碼越熟悉,就越容易從對這些技術的零散瞭解轉變到真正把握這些技術(同時也真正打開了 Web 應用程序開發的大門)。
要了解的一個對象可能對您來說也是最陌生的,即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
的強大之處。它可以根據需要自行與服務器進行交互,用戶甚至可以完全不知道幕後發生的一切。結果就是類似於桌面應用程序的動態、快速響應、高交互性的體驗,但是背後又擁有互聯網的全部強大力量。
得到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 有點兒令人生畏,HTML 設計者很少使用它,即使 JavaScript 程序員也不大用到它,除非要完成某項高端編程任務。大量使用 DOM 的是複雜的 Java 和 C/C++ 程序,這可能就是 DOM 被認爲難以學習的原因。
幸運的是,在 JavaScript 技術中使用 DOM 很容易,也非常直觀。現在,按照常規也許應該說明如何使用 DOM,或者至少要給出一些示例代碼,但這樣做也可能誤導您。即使不理會 DOM,仍然能深入地探討 Ajax,這也是我準備採用的方法。以後的文章將再次討論 DOM,現在只要知道可能需要 DOM 就可以了。當需要在 JavaScript 代碼和服務器之間傳遞 XML 和改變 HTML 表單的時候,我們再深入研究 DOM。沒有它也能做一些有趣的工作,因此現在就把 DOM 放到一邊吧。
有了上面的基礎知識後,我們來看看一些具體的例子。XMLHttpRequest
是 Ajax 應用程序的核心,而且對很多讀者來說可能還比較陌生,我們就從這裏開始吧。從清單 1 可以看出,創建和使用這個對象非常簡單,不是嗎?等一等。
還記得幾年前的那些討厭的瀏覽器戰爭嗎?沒有一樣東西在不同的瀏覽器上得到同樣的結果。不管您是否相信,這些戰爭仍然在繼續,雖然規模較小。但令人奇怪的是,XMLHttpRequest
成了這場戰爭的犧牲品之一。因此獲得XMLHttpRequest
對象可能需要採用不同的方法。下面我將詳細地進行解釋。
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 瀏覽器中創建該對象:
o 嘗試使用Msxml2.XMLHTTP
對象創建它。
o 如果失敗,再嘗試Microsoft.XMLHTTP
對象。
3. 如果仍然沒有建立xmlHttp
,則以非 Microsoft 的方式創建該對象。
最後,xmlHttp
應該引用一個有效的XMLHttpRequest
對象,無論運行什麼樣的瀏覽器。
安全性如何呢?現在瀏覽器允許用戶提高他們的安全等級,關閉 JavaScript 技術,禁用瀏覽器中的任何選項。在這種情況下,代碼無論如何都不會工作。此時必須適當地處理問題,這需要單獨的一篇文章來討論,要放到以後了(這個系列夠長了吧?不用擔心,讀完之前也許您就掌握了)。現在要編寫一段健壯但不夠完美的代碼,對於掌握 Ajax 來說就很好了。以後我們還將討論更多的細節。
現在我們介紹了 Ajax,對XMLHttpRequest
對象以及如何創建它也有了基本的瞭解。如果閱讀得很仔細,您可能已經知道與服務器上的 Web 應用程序打交道的是 JavaScript 技術,而不是直接提交給那個應用程序的 HTML 表單。
還缺少什麼呢?到底如何使用XMLHttpRequest
。因爲這段代碼非常重要,您編寫的每個 Ajax 應用程序都要以某種形式使用它,先看看 Ajax 的基本請求/響應模型是什麼樣吧。
您已經有了一個嶄新的XMLHttpRequest
對象,現在讓它乾點活兒吧。首先需要一個 Web 頁面能夠調用的 JavaScript 方法(比如當用戶輸入文本或者從菜單中選擇一項時)。接下來就是在所有 Ajax 應用程序中基本都雷同的流程:
1. 從 Web 表單中獲取需要的數據。
2. 建立要連接的 URL。
3. 打開到服務器的連接。
4. 設置服務器在完成後要運行的函數。
5. 發送請求。
清單 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 中發送的數據調用。
function updatePage() {
if (xmlHttp.readyState == 4) {
var response = xmlHttp.responseText;
document.getElementById("zipCode").value = response;
}
}
|
這些代碼同樣既不難也不復雜。它等待服務器調用,如果是就緒狀態,則使用服務器返回的值(這裏是用戶輸入的城市和州的 ZIP 編碼)設置另一個表單字段的值。於是包含 ZIP 編碼的zipCode
字段突然出現了,而用戶沒有按任何按鈕!這就是前面所說的桌面應用程序的感覺。快速響應、動態感受等等,這些都只因爲有了小小的一段 Ajax 代碼。
細心的讀者可能注意到zipCode
是一個普通的文本字段。一旦服務器返回 ZIP 編碼,updatePage()
方法就用城市/州的 ZIP 編碼設置那個字段的值,用戶就可以改寫該值。這樣做有兩個原因:保持例子簡單,說明有時候可能希望用戶能夠修改服務器返回的數據。要記住這兩點,它們對於好的用戶界面設計來說很重要。
還有什麼呢?實際上沒有多少了。一個 JavaScript 方法捕捉用戶輸入表單的信息並將其發送到服務器,另一個 JavaScript 方法監聽和處理響應,並在響應返回時設置字段的值。所有這些實際上都依賴於調用第一個 JavaScript 方法,它啓動了整個過程。最明顯的辦法是在 HTML 表單中增加一個按鈕,但這是 2001 年的辦法,您不這樣認爲嗎?還是像清單 7 這樣利用 JavaScript 技術吧。
<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 開始運行了。有點兒明白怎麼回事了吧?好,就是如此!