本系列的上一期文章(請參閱 參考資料 中的鏈接),我們介紹了 Ajax 應(yīng)用程序,考察了推動 Ajax 應(yīng)用程序的基本概念。其中的核心是很多您可能已經(jīng)了解的技術(shù):JavaScript、HTML 和 XHTML、一點動態(tài) HTML 以及 DOM(文檔對象模型)。本文將放大其中的一點,把目光放到具體的 Ajax 細(xì)節(jié)上。
本文中,您將開始接觸最基本和基礎(chǔ)性的有關(guān) Ajax 的全部對象和編程方法:
XMLHttpRequest
對象。該對象實際上僅僅是一個跨越所有 Ajax 應(yīng)用程序的公共線程,您可能已經(jīng)預(yù)料到,只有徹底理解該對象才能充分發(fā)揮編程的潛力。事實上,有時您會發(fā)現(xiàn),要正確地使用
XMLHttpRequest
,顯然
不能
使用
XMLHttpRequest
。這到底是怎么回事呢?
在深入研究代碼之前首先看看最近的觀點 —— 一定要十分清楚 Web 2.0 這個概念。聽到 Web 2.0 這個詞的時候,應(yīng)該首先問一問 “Web 1.0 是什么?” 雖然很少聽人提到 Web 1.0,實際上它指的就是具有完全不同的請求和響應(yīng)模型的傳統(tǒng) Web。比如,到 Amazon.com 網(wǎng)站上點擊一個按鈕或者輸入搜索項。就會對服務(wù)器發(fā)送一個請求,然后響應(yīng)再返回到瀏覽器。該請求不僅僅是圖書和書目列表,而是另一個完整的 HTML 頁面。因此當(dāng) Web 瀏覽器用新的 HTML 頁面重繪時,可能會看到閃爍或抖動。事實上,通過看到的每個新頁面可以清晰地看到請求和響應(yīng)。
Web 2.0(在很大程度上)消除了這種看得見的往復(fù)交互。比如訪問 Google Maps 或 Flickr 這樣的站點(到這些支持 Web 2.0 和 Ajax 站點的鏈接請參閱 參考資料 )。比如在 Google Maps 上,您可以拖動地圖,放大和縮小,只有很少的重繪操作。當(dāng)然這里仍然有請求和響應(yīng),只不過都 藏到了幕后 。作為用戶,體驗更加舒適,感覺很像桌面應(yīng)用程序。這種新的感受和范型就是當(dāng)有人提到 Web 2.0 時您所體會到的。
需要關(guān)心的是如何使這些新的交互成為可能。顯然,仍然需要發(fā)出請求和接收響應(yīng),但正是針對每次請求/響應(yīng)交互的 HTML 重繪造成了緩慢、笨拙的 Web 交互的感受。因此很清楚,我們需要一種方法使發(fā)送的請求和接收的響應(yīng) 只 包含需要的數(shù)據(jù)而不是整個 HTML 頁面。惟一需要獲得整個新 HTML 頁面的時候就是希望用戶 看到 新頁面的時候。
但多數(shù)交互都是在已有頁面上增加細(xì)節(jié)、修改主體文本或者覆蓋原有數(shù)據(jù)。這些情況下,Ajax 和 Web 2.0 方法允許在 不 更新整個 HTML 頁面的情況下發(fā)送和接收數(shù)據(jù)。對于那些經(jīng)常上網(wǎng)的人,這種能力可以讓您的應(yīng)用程序感覺更快、響應(yīng)更及時,讓他們不時地光顧您的網(wǎng)站。
要真正實現(xiàn)這種絢麗的奇跡,必須非常熟悉一個 JavaScript 對象,即
XMLHttpRequest
。這個小小的對象實際上已經(jīng)在幾種瀏覽器中存在一段時間了,它是本專欄今后幾個月中要介紹的 Web 2.0、Ajax 和大部分其他內(nèi)容的核心。為了讓您快速地大體了解它,下面給出將要用于該對象的
很少的幾個
方法和屬性。
-
open()
:建立到服務(wù)器的新請求。 -
send()
:向服務(wù)器發(fā)送請求。 -
abort()
:退出當(dāng)前請求。 -
readyState
:提供當(dāng)前 HTML 的就緒狀態(tài)。 -
responseText
:服務(wù)器返回的請求響應(yīng)文本。
如果不了解這些(或者其中的
任何
一個),您也不用擔(dān)心,后面幾篇文章中我們將介紹每個方法和屬性。現(xiàn)在
應(yīng)該
了解的是,明確用
XMLHttpRequest
做什么。要注意這些方法和屬性都與發(fā)送請求及處理響應(yīng)有關(guān)。事實上,如果看到
XMLHttpRequest
的所有方法和屬性,就會發(fā)現(xiàn)它們
都
與非常簡單的請求/響應(yīng)模型有關(guān)。顯然,我們不會遇到特別新的 GUI 對象或者創(chuàng)建用戶交互的某種超極神秘的方法,我們將使用非常簡單的請求和非常簡單的響應(yīng)。聽起來似乎沒有多少吸引力,但是用好該對象可以徹底改變您的應(yīng)用程序。
首先需要創(chuàng)建一個新變量并賦給它一個
XMLHttpRequest
對象實例。這在 JavaScript 中很簡單,只要對該對象名使用
new
關(guān)鍵字即可,如
清單 1
所示。
清單 1. 創(chuàng)建新的 XMLHttpRequest 對象
<script language="javascript" type="text/javascript"> var request = new XMLHttpRequest(); </script> |
不難吧?記住,JavaScript 不要求指定變量類型,因此不需要像 清單 2 那樣做(在 Java 語言中可能需要這樣)。
清單 2. 創(chuàng)建 XMLHttpRequest 的 Java 偽代碼
XMLHttpRequest request = new XMLHttpRequest(); |
因此在 JavaScript 中用
var
創(chuàng)建一個變量,給它一個名字(如 “request”),然后賦給它一個新的
XMLHttpRequest
實例。此后就可以在函數(shù)中使用該對象了。
在實際上各種事情都可能出錯,而上面的代碼沒有提供任何錯誤處理。較好的辦法是創(chuàng)建該對象,并在出現(xiàn)問題時優(yōu)雅地退出。比如,任何較早的瀏覽器(不論您是否相信,仍然有人在使用老版本的 Netscape Navigator)都不支持
XMLHttpRequest
,您需要讓這些用戶知道有些地方出了問題。
清單 3
說明如何創(chuàng)建該對象,以便在出現(xiàn)問題的時候發(fā)出 JavaScript 警告。
清單 3. 創(chuàng)建具有錯誤處理能力的 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> |
一定要理解這些步驟:
-
創(chuàng)建一個新變量
request
并賦值 false。后面將使用 false 作為判定條件,它表示還沒有創(chuàng)建XMLHttpRequest
對象。 -
增加 try/catch 塊:
-
嘗試創(chuàng)建
XMLHttpRequest
對象。 -
如果失敗(
catch (failed)
)則保證request
的值仍然為 false。
-
嘗試創(chuàng)建
-
檢查
request
是否仍為 false(如果一切正常就不會是 false)。 -
如果出現(xiàn)問題(
request
是 false)則使用 JavaScript 警告通知用戶出現(xiàn)了問題。
代碼非常簡單,對大多數(shù) JavaScript 和 Web 開發(fā)人員來說,真正理解它要比讀寫代碼花更長的時間。現(xiàn)在已經(jīng)得到了一段帶有錯誤檢查的
XMLHttpRequest
對象創(chuàng)建代碼,還可以告訴您哪兒出了問題。
看起來似乎一切良好,至少在用 Internet Explorer 試驗這些代碼之前是這樣的。如果這樣試驗的話,就會看到 圖 1 所示的糟糕情形。
顯然有什么地方不對勁,而 Internet Explorer 很難說是一種過時的瀏覽器,因為全世界有 70% 在使用 Internet Explorer。換句話說,如果不支持 Microsoft 和 Internet Explorer 就不會受到 Web 世界的歡迎!因此我們需要采用不同的方法處理 Microsoft 瀏覽器。
經(jīng)驗證發(fā)現(xiàn) Microsoft 支持 Ajax,但是其
XMLHttpRequest
版本有不同的稱呼。事實上,它將其稱為
幾種
不同的東西。如果使用較新版本的 Internet Explorer,則需要使用對象
Msxml2.XMLHTTP
,而較老版本的 Internet Explorer 則使用
Microsoft.XMLHTTP
。我們需要支持這兩種對象類型(同時還要支持非 Microsoft 瀏覽器)。請看看
清單 4
,它在前述代碼的基礎(chǔ)上增加了對 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> |
很容易被這些花括號迷住了眼睛,因此下面分別介紹每一步:
-
創(chuàng)建一個新變量
request
并賦值 false。使用 false 作為判斷條件,它表示還沒有創(chuàng)建XMLHttpRequest
對象。 -
增加 try/catch 塊:
-
嘗試創(chuàng)建
XMLHttpRequest
對象。 -
如果失敗(
catch (trymicrosoft)
):-
嘗試使用較新版本的 Microsoft 瀏覽器創(chuàng)建 Microsoft 兼容的對象(
Msxml2.XMLHTTP
)。 -
如果失敗(
catch (othermicrosoft)
)嘗試使用較老版本的 Microsoft 瀏覽器創(chuàng)建 Microsoft 兼容的對象(Microsoft.XMLHTTP
)。
-
嘗試使用較新版本的 Microsoft 瀏覽器創(chuàng)建 Microsoft 兼容的對象(
-
如果失敗(
catch (failed)
)則保證request
的值仍然為 false。
-
嘗試創(chuàng)建
-
檢查
request
是否仍然為 false(如果一切順利就不會是 false)。 -
如果出現(xiàn)問題(
request
是 false)則使用 JavaScript 警告通知用戶出現(xiàn)了問題。
這樣修改代碼之后再使用 Internet Explorer 試驗,就應(yīng)該看到已經(jīng)創(chuàng)建的表單(沒有錯誤消息)。我實驗的結(jié)果如 圖 2 所示。
再看一看清單
1
、
3
和
4
,注意,所有這些代碼都直接嵌套在
script
標(biāo)記中。像這種不放到方法或函數(shù)體中的 JavaScript 代碼稱為
靜態(tài) JavaScript
。就是說代碼是在頁面顯示給用戶之前的某個時候運行。(雖然根據(jù)規(guī)范不能完全
精確地
知道這些代碼何時運行對瀏覽器有什么影響,但是可以保證這些代碼在用戶能夠與頁面交互之前運行。)這也是多數(shù) Ajax 程序員創(chuàng)建
XMLHttpRequest
對象的一般方式。
就是說,也可以像 清單 5 那樣將這些代碼放在一個方法中。
清單 5. 將 XMLHttpRequest 創(chuàng)建代碼移動到方法中
<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 之前需要調(diào)用該方法。因此還需要 清單 6 這樣的代碼。
清單 6. 使用 XMLHttpRequest 的創(chuàng)建方法
<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> |
此代碼惟一的問題是推遲了錯誤通知,這也是多數(shù) Ajax 程序員不采用這一方法的原因。假設(shè)一個復(fù)雜的表單有 10 或 15 個字段、選擇框等,當(dāng)用戶在第 14 個字段(按照表單順序從上到下)輸入文本時要激活某些 Ajax 代碼。這時候運行
getCustomerInfo()
嘗試創(chuàng)建一個
XMLHttpRequest
對象,但(對于本例來說)失敗了。然后向用戶顯示一條警告,明確地告訴他們不能使用該應(yīng)用程序。但用戶已經(jīng)花費了很多時間在表單中輸入數(shù)據(jù)!這是非常令人討厭的,而討厭顯然不會吸引用戶再次訪問您的網(wǎng)站。
如果使用靜態(tài) JavaScript,用戶在點擊頁面的時候很快就會看到錯誤信息。這樣也很煩人,是不是?可能令用戶錯誤地認(rèn)為您的 Web 應(yīng)用程序不能在他的瀏覽器上運行。不過,當(dāng)然要比他們花費了 10 分鐘輸入信息之后再顯示同樣的錯誤要好。因此,我建議編寫靜態(tài)的代碼,讓用戶盡可能早地發(fā)現(xiàn)問題。
得到請求對象之后就可以進(jìn)入請求/響應(yīng)循環(huán)了。記住,
XMLHttpRequest
惟一的目的是讓您發(fā)送請求和接收響應(yīng)。其他一切都是 JavaScript、CSS 或頁面中其他代碼的工作:改變用戶界面、切換圖像、解釋服務(wù)器返回的數(shù)據(jù)。準(zhǔn)備好
XMLHttpRequest
之后,就可以向服務(wù)器發(fā)送請求了。
Ajax 采用一種沙箱安全模型。因此,Ajax 代碼(具體來說就是
XMLHttpRequest
對象)只能對所在的同一個域發(fā)送請求。以后的文章中將進(jìn)一步介紹安全和 Ajax,現(xiàn)在只要知道在本地機(jī)器上運行的代碼只能對本地機(jī)器上的服務(wù)器端腳本發(fā)送請求。如果讓 Ajax 代碼在 www.breakneckpizza.com 上運行,則必須 www.breakneck.com 中運行的腳本發(fā)送請求。
首先要確定連接的服務(wù)器的 URL。這并不是 Ajax 的特殊要求,但仍然是建立連接所必需的,顯然現(xiàn)在您應(yīng)該知道如何構(gòu)造 URL 了。多數(shù)應(yīng)用程序中都會結(jié)合一些靜態(tài)數(shù)據(jù)和用戶處理的表單中的數(shù)據(jù)來構(gòu)造該 URL。比如, 清單 7 中的 JavaScript 代碼獲取電話號碼字段的值并用其構(gòu)造 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> |
這里沒有難懂的地方。首先,代碼創(chuàng)建了一個新變量
phone
,并把 ID 為 “phone” 的表單字段的值賦給它。
清單 8
展示了這個表單的 XHTML,其中可以看到
phone
字段及其
id
屬性。
<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> |
還要注意,當(dāng)用戶輸入電話號碼或者改變電話號碼時,將觸發(fā)
清單 8
所示的
getCustomerInfo()
方法。該方法取得電話號碼并構(gòu)造存儲在
url
變量中的 URL 字符串。記住,由于 Ajax 代碼是沙箱型的,因而只能連接到同一個域,實際上 URL 中不需要域名。該例中的腳本名為
/cgi-local/lookupCustomer.php
。最后,電話號碼作為 GET 參數(shù)附加到該腳本中:
"phone=" + escape(phone)
。
如果以前沒用見過
escape()
方法,它用于轉(zhuǎn)義不能用明文正確發(fā)送的任何字符。比如,電話號碼中的空格將被轉(zhuǎn)換成字符
%20
,從而能夠在 URL 中傳遞這些字符。
可以根據(jù)需要添加任意多個參數(shù)。比如,如果需要增加另一個參數(shù),只需要將其附加到 URL 中并用 “與”(
&
)字符分開 [第一個參數(shù)用問號(
?
)和腳本名分開]。
有了要連接的 URL 后就可以配置請求了。可以用
XMLHttpRequest
對象的
open()
方法來完成。該方法有五個參數(shù):
-
request-type
:發(fā)送請求的類型。典型的值是
GET
或POST
,但也可以發(fā)送HEAD
請求。 - url :要連接的 URL。
- asynch :如果希望使用異步連接則為 true,否則為 false。該參數(shù)是可選的,默認(rèn)為 true。
- username :如果需要身份驗證,則可以在此指定用戶名。該可選參數(shù)沒有默認(rèn)值。
- password :如果需要身份驗證,則可以在此指定口令。該可選參數(shù)沒有默認(rèn)值。
通常使用其中的前三個參數(shù)。事實上,即使需要異步連接,也應(yīng)該指定第三個參數(shù)為 “true”。這是默認(rèn)值,但堅持明確指定請求是異步的還是同步的更容易理解。
將這些結(jié)合起來,通常會得到 清單 9 所示的一行代碼。
function getCustomerInfo() { var phone = document.getElementById("phone").value; var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone); request.open("GET", url, true); } |
一旦設(shè)置好了 URL,其他就簡單了。多數(shù)請求使用
GET
就夠了(后面的文章中將看到需要使用
POST
的情況),再加上 URL,這就是使用
open()
方法需要的全部內(nèi)容了。
本系列的后面一篇文章中,我將用很多時間編寫和使用異步代碼,但是您應(yīng)該明白為什么
open()
的最后一個參數(shù)這么重要。在一般的請求/響應(yīng)模型中,比如 Web 1.0,客戶機(jī)(瀏覽器或者本地機(jī)器上運行的代碼)向服務(wù)器發(fā)出請求。該請求是同步的,換句話說,客戶機(jī)等待服務(wù)器的響應(yīng)。當(dāng)客戶機(jī)等待的時候,至少會用某種形式通知您在等待:
- 沙漏(特別是 Windows 上)。
- 旋轉(zhuǎn)的皮球(通常在 Mac 機(jī)器上)。
- 應(yīng)用程序基本上凍結(jié)了,然后過一段時間光標(biāo)變化了。
這正是 Web 應(yīng)用程序讓人感到笨拙或緩慢的原因 —— 缺乏真正的交互性。按下按鈕時,應(yīng)用程序?qū)嶋H上變得不能使用,直到剛剛觸發(fā)的請求得到響應(yīng)。如果請求需要大量服務(wù)器處理,那么等待的時間可能很長(至少在這個多處理器、DSL 沒有等待的世界中是如此)。
而異步請求 不 等待服務(wù)器響應(yīng)。發(fā)送請求后應(yīng)用程序繼續(xù)運行。用戶仍然可以在 Web 表單中輸入數(shù)據(jù),甚至離開表單。沒有旋轉(zhuǎn)的皮球或者沙漏,應(yīng)用程序也沒有明顯的凍結(jié)。服務(wù)器悄悄地響應(yīng)請求,完成后告訴原來的請求者工作已經(jīng)結(jié)束(具體的辦法很快就會看到)。結(jié)果是,應(yīng)用程序感覺 不 那么遲鈍或者緩慢,而是響應(yīng)迅速、交互性強(qiáng),感覺快多了。這僅僅是 Web 2.0 的一部分,但它是很重要的一部分。所有老套的 GUI 組件和 Web 設(shè)計范型都不能克服緩慢、同步的請求/響應(yīng)模型。
一旦用
open()
配置好之后,就可以發(fā)送請求了。幸運的是,發(fā)送請求的方法的名稱要比
open()
適當(dāng),它就是
send()
。
send()
只有一個參數(shù),就是要發(fā)送的內(nèi)容。但是在考慮這個方法之前,回想一下前面已經(jīng)通過 URL 本身發(fā)送過數(shù)據(jù)了:
var url = "/cgi-local/lookupCustomer.php? phone=" + escape(phone); |
雖然可以使用
send()
發(fā)送數(shù)據(jù),但也能通過 URL 本身發(fā)送數(shù)據(jù)。事實上,
GET
請求(在典型的 Ajax 應(yīng)用中大約占 80%)中,用 URL 發(fā)送數(shù)據(jù)要容易得多。如果需要發(fā)送安全信息或 XML,可能要考慮使用
send()
發(fā)送內(nèi)容(本系列的后續(xù)文章中將討論安全數(shù)據(jù)和 XML 消息)。如果不需要通過
send()
傳遞數(shù)據(jù),則只要傳遞
null
作為該方法的參數(shù)即可。因此您會發(fā)現(xiàn)在本文中的例子中只需要這樣發(fā)送請求(參見
清單 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); } |
現(xiàn)在我們所做的只有很少一點是新的、革命性的或異步的。必須承認(rèn),
open()
方法中 “true” 這個小小的關(guān)鍵字建立了異步請求。但是除此之外,這些代碼與用 Java servlet 及 JSP、PHP 或 Perl 編程沒有什么兩樣。那么 Ajax 和 Web 2.0 最大的秘密是什么呢?秘密就在于
XMLHttpRequest
的一個簡單屬性
onreadystatechange
。
首先一定要理解這些代碼中的流程(如果需要請回顧
清單 10
)。建立其請求然后發(fā)出請求。此外,因為是異步請求,所以 JavaScript 方法(例子中的
getCustomerInfo()
)不會等待服務(wù)器。因此代碼將繼續(xù)執(zhí)行,就是說,將退出該方法而把控制返回給表單。用戶可以繼續(xù)輸入信息,應(yīng)用程序不會等待服務(wù)器。
這就提出了一個有趣的問題:服務(wù)器完成了請求之后會發(fā)生什么?答案是什么也不發(fā)生,至少對現(xiàn)在的代碼而言如此!顯然這樣不行,因此服務(wù)器在完成通過
XMLHttpRequest
發(fā)送給它的請求處理之后需要某種指示說明怎么做。
現(xiàn)在
onreadystatechange
屬性該登場了。該屬性允許指定一個
回調(diào)函數(shù)
。回調(diào)允許服務(wù)器(猜得到嗎?)
反向調(diào)用
Web 頁面中的代碼。它也給了服務(wù)器一定程度的控制權(quán),當(dāng)服務(wù)器完成請求之后,會查看
XMLHttpRequest
對象,特別是
onreadystatechange
屬性。然后調(diào)用該屬性指定的任何方法。之所以稱為回調(diào)是因為服務(wù)器向網(wǎng)頁發(fā)起調(diào)用,無論網(wǎng)頁本身在做什么。比方說,可能在用戶坐在椅子上手沒有碰鍵盤的時候調(diào)用該方法,但是也可能在用戶輸入、移動鼠標(biāo)、滾動屏幕或者點擊按鈕時調(diào)用該方法。它并不關(guān)心用戶在做什么。
這就是稱之為異步的原因:用戶在一層上操作表單,而在另一層上服務(wù)器響應(yīng)請求并觸發(fā)
onreadystatechange
屬性指定的回調(diào)方法。因此需要像
清單 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); } |
需要特別注意的是該屬性在代碼中設(shè)置的
位置
—— 它是在調(diào)用
send()
之前
設(shè)置的。發(fā)送請求之前必須設(shè)置該屬性,這樣服務(wù)器在回答完成請求之后才能查看該屬性。現(xiàn)在剩下的就只有編寫
updatePage()
方法了,這是本文最后一節(jié)要討論的重點。
發(fā)送請求,用戶高興地使用 Web 表單(同時服務(wù)器在處理請求),而現(xiàn)在服務(wù)器完成了請求處理。服務(wù)器查看
onreadystatechange
屬性確定要調(diào)用的方法。除此以外,可以將您的應(yīng)用程序看作其他應(yīng)用程序一樣,無論是否異步。換句話說,不一定要采取特殊的動作編寫響應(yīng)服務(wù)器的方法,只需要改變表單,讓用戶訪問另一個 URL 或者做響應(yīng)服務(wù)器需要的任何事情。這一節(jié)我們重點討論對服務(wù)器的響應(yīng)和一種典型的動作 —— 即時改變用戶看到的表單中的一部分。
現(xiàn)在我們已經(jīng)看到如何告訴服務(wù)器完成后應(yīng)該做什么:將
XMLHttpRequest
對象的
onreadystatechange
屬性設(shè)置為要運行的函數(shù)名。這樣,當(dāng)服務(wù)器處理完請求后就會自動調(diào)用該函數(shù)。也不需要擔(dān)心該函數(shù)的任何參數(shù)。我們從一個簡單的方法開始,如
清單 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> |
它僅僅發(fā)出一些簡單的警告,告訴您服務(wù)器什么時候完成了任務(wù)。在自己的網(wǎng)頁中試驗這些代碼,然后在瀏覽器中打開(如果希望查看該例中的 XHTML,請參閱 清單 8 )。輸入電話號碼然后離開該字段,將看到一個彈出的警告窗口(如 圖 3 所示),但是點擊 OK 又出現(xiàn)了……
根據(jù)瀏覽器的不同,在表單停止彈出警告之前會看到兩次、三次甚至四次警告。這是怎么回事呢?原來我們還沒有考慮 HTTP 就緒狀態(tài),這是請求/響應(yīng)循環(huán)中的一個重要部分。
前面提到,服務(wù)器在完成請求之后會在
XMLHttpRequest
的
onreadystatechange
屬性中查找要調(diào)用的方法。這是真的,但還不完整。事實上,每當(dāng) HTTP 就緒狀態(tài)改變時它都會調(diào)用該方法。這意味著什么呢?首先必須理解 HTTP 就緒狀態(tài)。
HTTP 就緒狀態(tài)表示請求的狀態(tài)或情形。它用于確定該請求是否已經(jīng)開始、是否得到了響應(yīng)或者請求/響應(yīng)模型是否已經(jīng)完成。它還可以幫助確定讀取服務(wù)器提供的響應(yīng)文本或數(shù)據(jù)是否安全。在 Ajax 應(yīng)用程序中需要了解五種就緒狀態(tài):
-
0
:請求沒有發(fā)出(在調(diào)用
open()
之前)。 -
1
:請求已經(jīng)建立但還沒有發(fā)出(調(diào)用
send()
之前)。 - 2 :請求已經(jīng)發(fā)出正在處理之中(這里通常可以從響應(yīng)得到內(nèi)容頭部)。
- 3 :請求已經(jīng)處理,響應(yīng)中通常有部分?jǐn)?shù)據(jù)可用,但是服務(wù)器還沒有完成響應(yīng)。
- 4 :響應(yīng)已完成,可以訪問服務(wù)器響應(yīng)并使用它。
與大多數(shù)跨瀏覽器問題一樣,這些就緒狀態(tài)的使用也不盡一致。您也許期望任務(wù)就緒狀態(tài)從 0 到 1、2、3 再到 4,但實際上很少是這種情況。一些瀏覽器從不報告 0 或 1 而直接從 2 開始,然后是 3 和 4。其他瀏覽器則報告所有的狀態(tài)。還有一些則多次報告就緒狀態(tài) 1。在上一節(jié)中看到,服務(wù)器多次調(diào)用
updatePage()
,每次調(diào)用都會彈出警告框 —— 可能和預(yù)期的不同!
對于 Ajax 編程,需要直接處理的惟一狀態(tài)就是就緒狀態(tài) 4,它表示服務(wù)器響應(yīng)已經(jīng)完成,可以安全地使用響應(yīng)數(shù)據(jù)了。基于此,回調(diào)方法中的第一行應(yīng)該如 清單 13 所示。
function updatePage() { if (request.readyState == 4) alert("Server is done!"); } |
修改后就可以保證服務(wù)器的處理已經(jīng)完成。嘗試運行新版本的 Ajax 代碼,現(xiàn)在就會看到與預(yù)期的一樣,只顯示一次警告信息了。
雖然 清單 13 中的代碼看起來似乎不錯,但是還有一個問題 —— 如果服務(wù)器響應(yīng)請求并完成了處理但是報告了一個錯誤怎么辦?要知道,服務(wù)器端代碼應(yīng)該明白它是由 Ajax、JSP、普通 HTML 表單或其他類型的代碼調(diào)用的,但只能使用傳統(tǒng)的 Web 專用方法報告信息。而在 Web 世界中,HTTP 代碼可以處理請求中可能發(fā)生的各種問題。
比方說,您肯定遇到過輸入了錯誤的 URL 請求而得到 404 錯誤碼的情形,它表示該頁面不存在。這僅僅是 HTTP 請求能夠收到的眾多錯誤碼中的一種(完整的狀態(tài)碼列表請參閱 參考資料 中的鏈接)。表示所訪問數(shù)據(jù)受到保護(hù)或者禁止訪問的 403 和 401 也很常見。無論哪種情況,這些錯誤碼都是從 完成的響應(yīng) 得到的。換句話說,服務(wù)器履行了請求(即 HTTP 就緒狀態(tài)是 4)但是沒有返回客戶機(jī)預(yù)期的數(shù)據(jù)。
因此除了就緒狀態(tài)外,還需要檢查 HTTP 狀態(tài)。我們期望的狀態(tài)碼是 200,它表示一切順利。如果就緒狀態(tài)是 4 而且狀態(tài)碼是 200,就可以處理服務(wù)器的數(shù)據(jù)了,而且這些數(shù)據(jù)應(yīng)該就是要求的數(shù)據(jù)(而不是錯誤或者其他有問題的信息)。因此還要在回調(diào)方法中增加狀態(tài)檢查,如 清單 14 所示。
function updatePage() { if (request.readyState == 4) if (request.status == 200) alert("Server is done!"); } |
為了增加更健壯的錯誤處理并盡量避免過于復(fù)雜,可以增加一兩個狀態(tài)碼檢查,請看一看
清單 15
中修改后的
updatePage()
版本。
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); } |
現(xiàn)在將
getCustomerInfo()
中的 URL 改為不存在的 URL 看看會發(fā)生什么。應(yīng)該會看到警告信息說明要求的 URL 不存在 —— 好極了!很難處理所有的錯誤條件,但是這一小小的改變能夠涵蓋典型 Web 應(yīng)用程序中 80% 的問題。
現(xiàn)在可以確保請求已經(jīng)處理完成(通過就緒狀態(tài)),服務(wù)器給出了正常的響應(yīng)(通過狀態(tài)碼),最后我們可以處理服務(wù)器返回的數(shù)據(jù)了。返回的數(shù)據(jù)保存在
XMLHttpRequest
對象的
responseText
屬性中。
關(guān)于
responseText
中的文本內(nèi)容,比如格式和長度,有意保持含糊。這樣服務(wù)器就可以將文本設(shè)置成任何內(nèi)容。比方說,一種腳本可能返回逗號分隔的值,另一種則使用管道符(即
|
字符)分隔的值,還有一種則返回長文本字符串。何去何從由服務(wù)器決定。
在本文使用的例子中,服務(wù)器返回客戶的上一個訂單和客戶地址,中間用管道符分開。然后使用訂單和地址設(shè)置表單中的元素值, 清單 16 給出了更新顯示內(nèi)容的代碼。
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, " |
首先,得到
responseText
并使用 JavaScript
split()
方法從管道符分開。得到的數(shù)組放到
response
中。數(shù)組中的第一個值 —— 上一個訂單 —— 用
response[0]
訪問,被設(shè)置為 ID 為 “order” 的字段的值。第二個值
response[1]
,即客戶地址,則需要更多一點處理。因為地址中的行用一般的行分隔符(“/n”字符)分隔,代碼中需要用 XHTML 風(fēng)格的行分隔符
<br />
來代替。替換過程使用
replace()
函數(shù)和正則表達(dá)式完成。最后,修改后的文本作為 HTML 表單
div
中的內(nèi)部 HTML。結(jié)果就是表單突然用客戶信息更新了,如圖 4 所示。
圖 4. 收到客戶數(shù)據(jù)后的 Break Neck 表單
結(jié)束本文之前,我還要介紹
XMLHttpRequest
的另一個重要屬性
responseXML
。如果服務(wù)器選擇使用 XML 響應(yīng)則該屬性包含(也許您已經(jīng)猜到)XML 響應(yīng)。處理 XML 響應(yīng)和處理普通文本有很大不同,涉及到解析、文檔對象模型(DOM)和其他一些問題。后面的文章中將進(jìn)一步介紹 XML。但是因為
responseXML
通常和
responseText
一起討論,這里有必要提一提。對于很多簡單的 Ajax 應(yīng)用程序
responseText
就夠了,但是您很快就會看到通過 Ajax 應(yīng)用程序也能很好地處理 XML。
您可能對
XMLHttpRequest
感到有點厭倦了,我很少看到一整篇文章討論一個對象,特別是這種簡單的對象。但是您將在使用 Ajax 編寫的每個頁面和應(yīng)用程序中反復(fù)使用該對象。坦白地說,關(guān)于
XMLHttpRequest
還真有一些可說的內(nèi)容。下一期文章中將介紹如何在請求中使用
POST
及
GET
,來設(shè)置請求中的內(nèi)容頭部和從服務(wù)器響應(yīng)讀取內(nèi)容頭部,理解如何在請求/響應(yīng)模型中編碼請求和處理 XML。
再往后我們將介紹常見 Ajax 工具箱。這些工具箱實際上隱藏了本文所述的很多細(xì)節(jié),使得 Ajax 編程更容易。您也許會想,既然有這么多工具箱為何還要對底層的細(xì)節(jié)編碼。答案是,如果不知道應(yīng)用程序在做 什么 ,就很難發(fā)現(xiàn)應(yīng)用程序中的問題。
因此不要忽略這些細(xì)節(jié)或者簡單地瀏覽一下,如果便捷華麗的工具箱出現(xiàn)了錯誤,您就不必?fù)项^或者發(fā)送郵件請求支持了。如果了解如何直接使用
XMLHttpRequest
,就會發(fā)現(xiàn)很容易調(diào)試和解決最奇怪的問題。只有讓其解決您的問題,工具箱才是好東西。
因此請熟悉
XMLHttpRequest
吧。事實上,如果您有使用工具箱的 Ajax 代碼,可以嘗試使用
XMLHttpRequest
對象及其屬性和方法重新改寫。這是一種不錯的練習(xí),可以幫助您更好地理解其中的原理。
下一期文章中將進(jìn)一步討論該對象,探討它的一些更有趣的屬性(如
responseXML
),以及如何使用
POST
請求和以不同的格式發(fā)送數(shù)據(jù)。請開始編寫代碼吧,一個月后我們再繼續(xù)討論。
學(xué)習(xí)
-
您可以參閱本文在 developerWorks 全球站點上的
英文原文
。
-
掌握 Ajax,第 1 部分: Ajax 簡介
(developerWorks,2005 年 12 月)幫助您了解 Ajax,這是一種構(gòu)建網(wǎng)站的高生產(chǎn)率方法。(本文中的參考資料列表都值得訪問!)
-
面向 Java 開發(fā)人員的 Ajax: 構(gòu)建動態(tài)的 Java 應(yīng)用程序
(developerWorks,2005 年 9 月)從 Java 的角度考察了 Ajax 服務(wù)器端。
-
面向 Java 開發(fā)人員的 Ajax: Ajax 的 Java 對象序列化
(developerWorks,2005 年 10 月)從 Java 的角度分析了如何通過網(wǎng)絡(luò)發(fā)送對象以及與 Ajax 交互。
-
使用 AJAX 調(diào)用 SOAP Web 服務(wù),第 1 部分: 構(gòu)建 Web 服務(wù)客戶機(jī)
(developerWorks,2005 年 10 月)是關(guān)于集成 Ajax 和現(xiàn)有基于 SOAP 的 web 服務(wù)的相當(dāng)高級的文章。
-
Google GMail
是一個很好的例子,說明了基于 Ajax 的應(yīng)用程序如何改變 Web 的工作方式。
-
Google Maps
是另一種基于 Google 的 Web 2.0 應(yīng)用程序。
-
Flickr
是一個很好的例子,說明如何使用 Ajax 創(chuàng)建類似桌面的 Web 應(yīng)用程序。
-
Ajax: A New Approach to Web Applications
發(fā)明了 Ajax 一詞,所有 Ajax 開發(fā)人員都應(yīng)該讀一讀。
-
Why Ajax Matters Now
告訴您為什么 Ajax 很重要。
-
如果使用的是 Microsoft 瀏覽器 Internet Explorer,請訪問
Microsoft Developer Network's XML Developer Center
。
-
請通過
在線文檔
進(jìn)一步了解 MSXML,Microsoft XML 解析器。
-
看一看響應(yīng)中包含的所有
HTTP 狀態(tài)碼
列表。
-
developerWorks
Web Architecture 專區(qū)
專門發(fā)表各種基于 Web 的解決方案的文章。
獲得產(chǎn)品和技術(shù)
-
Elisabeth Freeman、Eric Freeman 和 Brett McLaughlin 合著的
Head Rush Ajax
(2005 年 2 月,O'Reilly Media, Inc.)以 Head First 風(fēng)格將本文中所述的內(nèi)容灌輸?shù)侥念^腦中。
-
Java and XML
, Second Edition(Brett McLaughlin,2001 年 8 月,O'Reilly Media, Inc.)包括作者關(guān)于 XHTML 和 XML 轉(zhuǎn)換的討論。
-
JavaScript: The Definitive Guide
(David Flanagan,2001 年 11 月,O'Reilly Media, Inc.)詳細(xì)介紹了如何使用 JavaScript、動態(tài)網(wǎng)頁,第二版增加了關(guān)于 Ajax 的兩章。
- Head First HTML with CSS & XHTML (Elizabeth 和Eric Freeman,2005 年 12 月,O'Reilly Media, Inc.)是學(xué)習(xí) XHTML、CSS 以及如何將兩者結(jié)合起來的完整參考。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
