下面是我最近寫(xiě)的一個(gè)簡(jiǎn)單的類(lèi):
示例:
源代碼里有中文的注釋?zhuān)莂nsi的,如果你的網(wǎng)站或ie下選擇編碼為UTF-8,可能會(huì)導(dǎo)致錯(cuò)誤,你把你ie的編碼改成gb2312看看,或者去掉文件里的中文注釋
xmlhttp:onreadystatechange屬性
onreadystatechange
指定當(dāng)readyState屬性改變時(shí)的事件處理句柄
語(yǔ)法
oXMLHttpRequest.onreadystatechange = funcMyHandler;
Example
如下的例子演示當(dāng)XMLHTTPRequest對(duì)象的readyState屬性改變時(shí)調(diào)用HandleStateChange函數(shù),當(dāng)數(shù)據(jù)接收完畢后(readystate == 4)此頁(yè)面上的一個(gè)按鈕將被激活
var xmlhttp=null;
function PostOrder(xmldoc)
{
var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP.5.0");
xmlhttp.Open("POST", "http://myserver/orders/processorder.asp", false);
xmlhttp.onreadystatechange= HandleStateChange;
xmlhttp.Send(xmldoc);
myButton.disabled = true;
}
function HandleStateChange()
{
if (xmlhttp.readyState == 4)
{
myButton.disabled = false;
alert("Result = " + xmlhttp.responseXML.xml);
}
}
?
XMLHttpRequest對(duì)象
XMLHttpRequest
提供客戶(hù)端同http服務(wù)器通訊的協(xié)議
Example
下面的代碼是在JScript中創(chuàng)建一個(gè)XMLHTTP對(duì)象并從服務(wù)器請(qǐng)求一個(gè)XML文檔。服務(wù)器返回XML文檔并顯示。
var xmlHttpReq = new ActiveXObject("MSXML2.XMLHTTP.3.0");
xmlHttpReq.open("GET", "http://localhost/books.xml", false);
xmlHttpReq.send();
alert(xmlHttpReq.responseText);
在非IE的瀏覽器中,需要用new XMLHttpRequest()來(lái)創(chuàng)建對(duì)象,如下:
var xmlHttpReq = new XMLHttpRequest();
xmlHttpReq.open("GET", "http://localhost/books.xml", false);
xmlHttpReq.send();
alert(xmlHttpReq.responseText);
vbscript:
Dim HttpReq As New MSXML2.XMLHTTP30
HttpReq.open "GET", "http://localhost/books.xml", False
HttpReq.send
MsgBox HttpReq.responseText
備注
客戶(hù)端可以通過(guò)XmlHttp對(duì)象(MSXML2.XMLHTTP.3.0)向http服務(wù)器發(fā)送請(qǐng)求并使用微軟XML文檔對(duì)象模型Microsoft? XML Document Object Model (DOM)處理回應(yīng)。
xmlhttp:readyState屬性
readyState
返回XMLHTTP請(qǐng)求的當(dāng)前狀態(tài)
語(yǔ)法
lValue = oXMLHttpRequest.readyState;
Example
var XmlHttp;
XmlHttp = new ActiveXObject("Msxml2.XMLHTTP.3.0");
function send() {
XmlHttp.onreadystatechange = doHttpReadyStateChange;
XmlHttp.open("GET", "http://localhost/sample.xml", true);
XmlHttp.send();
}
function doHttpReadyStateChange() {
if (XmlHttp.readyState == 4) {
alert("Done");
}
}
備注
變量,此屬性只讀,狀態(tài)用長(zhǎng)度為4的整型表示.定義如下:
0 (未初始化) 對(duì)象已建立,但是尚未初始化(尚未調(diào)用open方法)
1 (初始化) 對(duì)象已建立,尚未調(diào)用send方法
2 (發(fā)送數(shù)據(jù)) send方法已調(diào)用,但是當(dāng)前的狀態(tài)及http頭未知
3 (數(shù)據(jù)傳送中) 已接收部分?jǐn)?shù)據(jù),因?yàn)轫憫?yīng)及http頭不全,這時(shí)通過(guò)responseBody和responseText獲取部分?jǐn)?shù)據(jù)會(huì)出現(xiàn)錯(cuò)誤,
4 (完成) 數(shù)據(jù)接收完畢,此時(shí)可以通過(guò)通過(guò)responseBody和responseText獲取完整的回應(yīng)數(shù)據(jù)
?
xmlhttp:responsebody屬性
responseBody
返回某一格式的服務(wù)器響應(yīng)數(shù)據(jù)
語(yǔ)法
strValue = oXMLHttpRequest.responseBody;
Example
var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP.3.0");
xmlhttp.open("GET", "http://localhost/books.xml", false);
xmlhttp.send();
alert(xmlhttp.responseBody);
備注
變量,此屬性只讀,以u(píng)nsigned array格式表示直接從服務(wù)器返回的未經(jīng)解碼的二進(jìn)制數(shù)據(jù)。
參考
responseStream 屬性
responseText 屬性
?
xmlhttp:responsestream屬性
responseStream
以Ado Stream對(duì)象的形式返回響應(yīng)信息
語(yǔ)法
strValue = oXMLHttpRequest.responseStream;
備注
變量,此屬性只讀,以Ado Stream對(duì)象的形式返回響應(yīng)信息。
?
xmlhttp:responsetext屬性
responseText
將響應(yīng)信息作為字符串返回
語(yǔ)法
strValue = oXMLHttpRequest.responseText;
Example
var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP.3.0");
xmlhttp.open("GET", "http://localhost/books.xml", false);
xmlhttp.send();
alert(xmlhttp.responseText);
備注
變量,此屬性只讀,將響應(yīng)信息作為字符串返回。
XMLHTTP嘗試將響應(yīng)信息解碼為Unicode字符串,XMLHTTP默認(rèn)將響應(yīng)數(shù)據(jù)的編碼定為UTF-8,如果服務(wù)器返回的數(shù)據(jù)帶BOM(byte -order mark),XMLHTTP可以解碼任何UCS-2 (big or little endian)或者UCS-4 數(shù)據(jù)。注意,如果服務(wù)器返回的是xml文檔,此屬性并不處理xml文檔中的編碼聲明。你需要使用responseXML來(lái)處理。
?
xmlhttprequest:responsexml成員
responseXML
將響應(yīng)信息格式化為Xml Document對(duì)象并返回
語(yǔ)法
var objDispatch = oXMLHttpRequest.responseXML;
Example
var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP.3.0");
xmlhttp.open("GET", "http://localhost/books.xml", false);
xmlhttp.send();
alert(xmlhttp.responseXML.xml);
備注
變量,此屬性只讀,將響應(yīng)信息格式化為Xml Document對(duì)象并返回。如果響應(yīng)數(shù)據(jù)不是有效的XML文檔,此屬性本身不返回XMLDOMParseError,可以通過(guò)處理過(guò)的DOMDocument對(duì)象獲取錯(cuò)誤信息。
?
xmlhttprequest對(duì)象:status成員
status
返回當(dāng)前請(qǐng)求的http狀態(tài)碼
語(yǔ)法
lValue = oXMLHttpRequest.status;
Example
var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP.3.0");
xmlhttp.open("GET", "http://localhost/books.xml", false);
xmlhttp.send();
alert(xmlhttp.status);
返回值
長(zhǎng)整形標(biāo)準(zhǔn)http狀態(tài)碼,定義如下:
Number Description
100
Continue
101
Switching protocols
200
OK
201
Created
202
Accepted
203
Non-Authoritative Information
204
No Content
205
Reset Content
206
Partial Content
300
Multiple Choices
301
Moved Permanently
302
Found
303
See Other
304
Not Modified
305
Use Proxy
307
Temporary Redirect
400
Bad Request
401
Unauthorized
402
Payment Required
403
Forbidden
404
Not Found
405
Method Not Allowed
406
Not Acceptable
407
Proxy Authentication Required
408
Request Timeout
409
Conflict
410
Gone
411
Length Required
412
Precondition Failed
413
Request Entity Too Large
414
Request-URI Too Long
415
Unsupported Media Type
416
Requested Range Not Suitable
417
Expectation Failed
500
Internal Server Error
501
Not Implemented
502
Bad Gateway
503
Service Unavailable
504
Gateway Timeout
505
HTTP Version Not Supported
備注
長(zhǎng)整形,此屬性只讀,返回當(dāng)前請(qǐng)求的http狀態(tài)碼,此屬性?xún)H當(dāng)數(shù)據(jù)發(fā)送并接收完畢后才可獲取。
?
xmlhttprequest:statusText成員
statusText
返回當(dāng)前請(qǐng)求的響應(yīng)行狀態(tài)
語(yǔ)法
strValue = oXMLHttpRequest.statusText;
Example
var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP.3.0");
xmlhttp.open("GET", "http://localhost/books.xml", false);
xmlhttp.send();
alert(xmlhttp.statusText);
備注
字符串,此屬性只讀,以BSTR返回當(dāng)前請(qǐng)求的響應(yīng)行狀態(tài),此屬性?xún)H當(dāng)數(shù)據(jù)發(fā)送并接收完畢后才可獲取。
?
取消當(dāng)前請(qǐng)求
語(yǔ)法
oXMLHttpRequest.abort();
備注
調(diào)用此方法后,當(dāng)前請(qǐng)求返回UNINITIALIZED 狀態(tài)。
?
xmlhttp:getallresponseheaders方法
getallresponseheaders
獲取響應(yīng)的所有http頭
語(yǔ)法
strValue = oXMLHttpRequest.getAllResponseHeaders();
Example
var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP.3.0");
xmlhttp.open("GET", "http://localhost/sample.xml", false);
xmlhttp.send();
alert(xmlhttp.getAllResponseHeaders());
輸出由web服務(wù)器返回的http頭信息,example:
Server:Microsoft-IIS/5.1
X-Powered-By:ASP.NET
Date:Sat, 07 Jun 2003 23:23:06 GMT
Content-Type:text/xml
Accept-Ranges:bytes
Last Modified:Sat, 06 Jun 2003 17:19:04 GMT
ETag:"a0e2eeba4f2cc31:97f"
Content-Length:9
備注
每個(gè)http頭名稱(chēng)和值用冒號(hào)分割,并以\r\n結(jié)束。當(dāng)send方法完成后才可調(diào)用該方法。
?
xmlhttp:getResponseHeader方法
getResponseHeader
從響應(yīng)信息中獲取指定的http頭
語(yǔ)法
strValue = oXMLHttpRequest.getResponseHeader(bstrHeader);
Example
var xmlhttp = new ActiveXObject("MSXML2.XMLHTTP.3.0");
xmlhttp.open("GET", "http://localhost/sample.xml", false);
xmlhttp.send();
alert(xmlhttp.getResponseHeader("Server"));
輸出http頭中的server列:當(dāng)前web服務(wù)器的版本及名稱(chēng)。
備注
當(dāng)send方法成功后才可調(diào)用該方法。如果服務(wù)器返回的文檔類(lèi)型為"text/xml", 則這句話xmlhttp.getResponseHeader("Content-Type");將返回字符串"text/xml"。可以使用 getAllResponseHeaders方法獲取完整的http頭信息。
?
創(chuàng)建一個(gè)新的http請(qǐng)求,并指定此請(qǐng)求的方法、URL以及驗(yàn)證信息
語(yǔ)法
oXMLHttpRequest.open(bstrMethod, bstrUrl, varAsync, bstrUser, bstrPassword);
參數(shù)
bstrMethod
http方法,例如:POST、GET、PUT及PROPFIND。大小寫(xiě)不敏感。
bstrUrl
請(qǐng)求的URL地址,可以為絕對(duì)地址也可以為相對(duì)地址。
varAsync[可選]
布爾型,指定此請(qǐng)求是否為異步方式,默認(rèn)為true。如果為真,當(dāng)狀態(tài)改變時(shí)會(huì)調(diào)用onreadystatechange屬性指定的回調(diào)函數(shù)。
bstrUser[可選]
如果服務(wù)器需要驗(yàn)證,此處指定用戶(hù)名,如果未指定,當(dāng)服務(wù)器需要驗(yàn)證時(shí),會(huì)彈出驗(yàn)證窗口。
bstrPassword[可選]
驗(yàn)證信息中的密碼部分,如果用戶(hù)名為空,則此值將被忽略。
Example
下面的例子演示從服務(wù)器請(qǐng)求book.xml,并顯示其中的book字段。
var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP.3.0");
xmlhttp.open("GET","http://localhost/books.xml", false);
xmlhttp.send();
var book = xmlhttp.responseXML.selectSingleNode("http://book[@id=''bk101'']");
alert(book.xml);
備注
調(diào)用此方法后,可以調(diào)用send方法向服務(wù)器發(fā)送數(shù)據(jù)。
?
發(fā)送請(qǐng)求到http服務(wù)器并接收回應(yīng)
語(yǔ)法
oXMLHttpRequest.send(varBody);
參數(shù)
varBody
欲通過(guò)此請(qǐng)求發(fā)送的數(shù)據(jù)。
Example
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP.3.0");
xmlhttp.open("GET", "http://localhost/sample.xml", false);
xmlhttp.send();
alert(xmlhttp.responseXML.xml);
備注
此方法的同步或異步方式取決于open方法中的bAsync參數(shù),如果bAsync == False,此方法將會(huì)等待請(qǐng)求完成或者超時(shí)時(shí)才會(huì)返回,如果bAsync == True,此方法將立即返回。
This method takes one optional parameter, which is the requestBody to use. The acceptable VARIANT input types are BSTR, SAFEARRAY of UI1 (unsigned bytes), IDispatch to an XML Document Object Model (DOM) object, and IStream *. You can use only chunked encoding (for sending) when sending IStream * input types. The component automatically sets the Content-Length header for all but IStream * input types.
如果發(fā)送的數(shù)據(jù)為BSTR,則回應(yīng)被編碼為utf-8, 必須在適當(dāng)位置設(shè)置一個(gè)包含charset的文檔類(lèi)型頭。
If the input type is a SAFEARRAY of UI1, the response is sent as is without additional encoding. The caller must set a Content-Type header with the appropriate content type.
如果發(fā)送的數(shù)據(jù)為XML DOM object,則回應(yīng)將被編碼為在xml文檔中聲明的編碼,如果在xml文檔中沒(méi)有聲明編碼,則使用默認(rèn)的UTF-8。
If the input type is an IStream *, the response is sent as is without additional encoding. The caller must set a Content-Type header with the appropriate content type.
?
xmlhttp:setrequestheader方法
setRequestHeader
單獨(dú)指定請(qǐng)求的某個(gè)http頭
語(yǔ)法
oXMLHttpRequest.setRequestHeader(bstrHeader, bstrValue);
參數(shù)
bstrHeader
字符串,頭名稱(chēng)。
bstrValue
字符串,值。
備注
如果已經(jīng)存在已此名稱(chēng)命名的http頭,則覆蓋之。此方法必須在open方法后調(diào)用。
?
xmlhttp的請(qǐng)求同步和異步、方法的get和post
http://www.niceidea.org/post/xmlhttp_true_false_post_get.html
看看open方法的另外幾個(gè)參數(shù)。
.open http-method, url, async, userID, password (后面是帳號(hào)和密碼,在禁止匿名訪問(wèn)的http頁(yè)面中,需要用戶(hù)名和口令)
首先看看異步處理方式。
其中async是一個(gè)布爾值。如果是異步通信方式(true),客戶(hù)機(jī)就不等待服務(wù)器的響應(yīng);如果是同步方式(false),客戶(hù)機(jī)就要等到服務(wù)器返回消息后才去執(zhí)行其他操作。我們需要根據(jù)實(shí)際需要來(lái)指定同步方式,在某些頁(yè)面中,可能會(huì)發(fā)出多個(gè)請(qǐng)求,甚至是有組織有計(jì)劃有隊(duì)形大規(guī)模的高強(qiáng)度的request,而后一個(gè)是會(huì)覆蓋前一個(gè)的,這個(gè)時(shí)候當(dāng)然要指定同步方式:Flase。
//niceidea 簽名留念
首先看看method,方法。
一個(gè)標(biāo)準(zhǔn)的http請(qǐng)求頭:
7/8/99 10:27:16 Sent GET /Store/Download.asp HTTP/1.1
Accept: application/msword, application/vnd.ms-execl, application/vnd.ms-
powerpoint, image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-
comet, */*
Accept-Language: en-us
Encoding: gzip, deflate
Referer: http://ww.wrox.com/main_menu.asp
Cookie: VisitCount=2&LASTDATE=6%2F4%2F99+10%3A10%3A13+AM
User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98)
Host: 212.250.238.67
Connection: Keep-Alive
很容易看懂,其中的method包括post/get/put等。對(duì)應(yīng)的主要是對(duì)于form即表單元素的處理方法。
當(dāng)mothod值為get時(shí),表單將附加在action頁(yè)面的url中;如果頁(yè)面是asp的,將會(huì)request.querystring中獲得;
如果是post,將會(huì)在request.form中獲得,
對(duì)應(yīng)與put方法的表單寫(xiě)法是:form method="POST" enctype='multipart/form-data'
主要用于上傳文件。
使用那種方法取決于服務(wù)端。
AJAX (異步 JavaScript 和 XML) 是個(gè)新產(chǎn)生的術(shù)語(yǔ),專(zhuān)為描述JavaScript的兩項(xiàng)強(qiáng)大性能.這兩項(xiàng)性能在多年來(lái)一直被網(wǎng)絡(luò)開(kāi)發(fā)者所忽略,直到最近Gmail, Google suggest和google Maps的橫空出世才使人們開(kāi)始意識(shí)到其重要性.
這兩項(xiàng)被忽視的性能是:
??無(wú)需重新裝載整個(gè)頁(yè)面便能向服務(wù)器發(fā)送請(qǐng)求.
??對(duì)XML文檔的解析和處理.
步驟 1 – "請(qǐng)!" --- 如何發(fā)送一個(gè)HTTP請(qǐng)求
為了用JavaScript向服務(wù)器發(fā)送一個(gè)HTTP請(qǐng)求, 需要一個(gè)具備這種功能的類(lèi)實(shí)例. 這樣的類(lèi)首先由Internet Explorer以ActiveX對(duì)象引入, 被稱(chēng)為XMLHTTP. 后來(lái)Mozilla, Safari 和其他瀏覽器紛紛仿效, 提供了XMLHttpRequest類(lèi),它支持微軟的ActiveX對(duì)象所提供的方法和屬性.
因此, 為了創(chuàng)建一個(gè)跨瀏覽器的這樣的類(lèi)實(shí)例(對(duì)象), 可以應(yīng)用如下代碼:
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
??? http_request = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE
??? http_request = new ActiveXObject("Microsoft.XMLHTTP");
}
(上例對(duì)代碼做了一定簡(jiǎn)化,這是為了解釋如何創(chuàng)建XMLHTTP類(lèi)實(shí)例. 實(shí)際的代碼實(shí)例可參閱本篇步驟3.)
如果服務(wù)器的響應(yīng)沒(méi)有XML mime-type header,某些Mozilla瀏覽器可能無(wú)法正常工作. 為了解決這個(gè)問(wèn)題, 如果服務(wù)器響應(yīng)的header不是text/xml,可以調(diào)用其它方法修改該header.
http_request = new XMLHttpRequest();
http_request.overrideMimeType('text/xml');
接下來(lái)要決定當(dāng)收到服務(wù)器的響應(yīng)后,需要做什么.這需要告訴HTTP請(qǐng)求對(duì)象用哪一個(gè)JavaScript函數(shù)處理這個(gè)響應(yīng).可以將對(duì)象的onreadystatechange屬性設(shè)置為要使用的JavaScript的函數(shù)名,如下所示:
http_request.onreadystatechange = nameOfTheFunction;
注意:在函數(shù)名后沒(méi)有括號(hào),也無(wú)需傳遞參數(shù).另外還有一種方法,可以在扉頁(yè)(fly)中定義函數(shù)及其對(duì)響應(yīng)要采取的行為,如下所示:
http_request.onreadystatechange = function(){
??? // do the thing
};
在定義了如何處理響應(yīng)后,就要發(fā)送請(qǐng)求了.可以調(diào)用HTTP請(qǐng)求類(lèi)的open()和send()方法, 如下所示:
http_request.open('GET', 'http://www.example.org/some.file', true);
http_request.send(null);
??open()的第一個(gè)參數(shù)是HTTP請(qǐng)求方式 – GET, POST, HEAD 或任何服務(wù)器所支持的您想調(diào)用的方式. 按照HTTP規(guī)范,該參數(shù)要大寫(xiě);否則,某些瀏覽器(如Firefox)可能無(wú)法處理請(qǐng)求.有關(guān)HTTP請(qǐng)求方法的詳細(xì)信息可參考http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html W3C specs
??第二個(gè)參數(shù)是請(qǐng)求頁(yè)面的URL.由于自身安全特性的限制,該頁(yè)面不能為第三方域名的頁(yè)面.同時(shí)一定要保證在所有的頁(yè)面中都使用準(zhǔn)確的域名,否則調(diào)用open()會(huì)得到"permission denied"的錯(cuò)誤提示.一個(gè)常見(jiàn)的錯(cuò)誤是訪問(wèn)站點(diǎn)時(shí)使用domain.tld,而當(dāng)請(qǐng)求頁(yè)面時(shí),卻使用www.domain.tld.
??第三個(gè)參數(shù)設(shè)置請(qǐng)求是否為異步模式.如果是TRUE, JavaScript函數(shù)將繼續(xù)執(zhí)行,而不等待服務(wù)器響應(yīng).這就是"AJAX"中的"A".
如果第一個(gè)參數(shù)是"POST",send()方法的參數(shù)可以是任何想送給服務(wù)器的數(shù)據(jù). 這時(shí)數(shù)據(jù)要以字符串的形式送給服務(wù)器,如下所示:
name=value&anothername=othervalue&so=on
步驟 2 – "收到!" --- 處理服務(wù)器的響應(yīng)
當(dāng)發(fā)送請(qǐng)求時(shí),要提供指定處理響應(yīng)的JavaScript函數(shù)名.
http_request.onreadystatechange = nameOfTheFunction;
我們來(lái)看看這個(gè)函數(shù)的功能是什么.首先函數(shù)會(huì)檢查請(qǐng)求的狀態(tài).如果狀態(tài)值是4,就意味著一個(gè)完整的服務(wù)器響應(yīng)已經(jīng)收到了,您將可以處理該響應(yīng).
if (http_request.readyState == 4) {
??? // everything is good, the response is received
} else {
??? // still not ready
}
readyState的取值如下:
??0 (未初始化)
??1 (正在裝載)
??2 (裝載完畢)
??3 (交互中)
??4 (完成)
(Source)
接著,函數(shù)會(huì)檢查HTTP服務(wù)器響應(yīng)的狀態(tài)值. 完整的狀態(tài)取值可參見(jiàn) W3C site. 我們著重看值為200 OK的響應(yīng).
i
f (http_request.status == 200) {
??? // perfect!
} else {
??? // there was a problem with the request,
??? // for example the response may be a 404 (Not Found)
??? // or 500 (Internal Server Error) response codes
}
在檢查完請(qǐng)求的狀態(tài)值和響應(yīng)的HTTP狀態(tài)值后, 您就可以處理從服務(wù)器得到的數(shù)據(jù)了.有兩種方式可以得到這些數(shù)據(jù):
??http_request.responseText – 以文本字符串的方式返回服務(wù)器的響應(yīng)
??http_request.responseXML – 以XMLDocument對(duì)象方式返回響應(yīng).處理XMLDocument對(duì)象可以用JavaScript DOM函數(shù)
步驟 3 – "萬(wàn)事俱備!" - 簡(jiǎn)單實(shí)例
我們現(xiàn)在將整個(gè)過(guò)程完整地做一次,發(fā)送一個(gè)簡(jiǎn)單的HTTP請(qǐng)求. 我們用JavaScript請(qǐng)求一個(gè)HTML文件, test.html, 文件的文本內(nèi)容為"I'm a test.".然后我們"alert()"test.html文件的內(nèi)容.
<script type="text/javascript" language="javascript">
??? var http_request = false;
??? function makeRequest(url) {
??????? http_request = false;
??????? if (window.XMLHttpRequest) { // Mozilla, Safari,...
??????????? http_request = new XMLHttpRequest();
??????????? if (http_request.overrideMimeType) {
??????????????? http_request.overrideMimeType('text/xml');
??????????? }
??????? } else if (window.ActiveXObject) { // IE
??????????? try {
??????????????? http_request = new ActiveXObject("Msxml2.XMLHTTP");
??????????? } catch (e) {
??????????????? try {
??????????????????? http_request = new ActiveXObject("Microsoft.XMLHTTP");
??????????????? } catch (e) {}
??????????? }
??????? }
??????? if (!http_request) {
??????????? alert('Giving up :( Cannot create an XMLHTTP instance');
??????????? return false;
??????? }
??????? http_request.onreadystatechange = alertContents;
??????? http_request.open('GET', url, true);
??????? http_request.send(null);
??? }
??? function alertContents() {
??????? if (http_request.readyState == 4) {
??????????? if (http_request.status == 200) {
??????????????? alert(http_request.responseText);
??????????? } else {
??????????????? alert('There was a problem with the request.');
??????????? }
??????? }
??? }
</script>
<span
??? style="cursor: pointer; text-decoration: underline"
??? onclick="makeRequest('test.html')">
??????? Make a request
</span>
本例中:
??用戶(hù)點(diǎn)擊瀏覽器上的"請(qǐng)求"鏈接;
??接著函數(shù)makeRequest()將被調(diào)用.其參數(shù) – HTML文件test.html在同一目錄下;
??這樣就發(fā)起了一個(gè)請(qǐng)求.onreadystatechange的執(zhí)行結(jié)果會(huì)被傳送給alertContents();
??alertContents()將檢查服務(wù)器的響應(yīng)是否成功地收到,如果是,就會(huì)"alert()"test.html文件的內(nèi)容.?
步驟 4 – "X-文檔" --- 處理XML響應(yīng)
在前面的例子中,當(dāng)服務(wù)器對(duì)HTTP請(qǐng)求的響應(yīng)被收到后,我們會(huì)調(diào)用請(qǐng)求對(duì)象的reponseText屬性.該屬性包含了test.html文件的內(nèi)容.現(xiàn)在我們來(lái)試試responseXML屬性.
首先,我們新建一個(gè)有效的XML文件,后面我們將使用這個(gè)文件.該文件(test.xml)源代碼如下所示:
<?xml version="1.0" ?>
<root>
??? I'm a test.
</root>
在該腳本中,我們只需修改請(qǐng)求部分:
...
onclick="makeRequest('test.xml')">
...
接著,在alertContents()中,我們將alert()的代碼alert(http_request.responseText);換成:
var xmldoc = http_request.responseXML;
var root_node = xmldoc.getElementsByTagName('root').item(0);
alert(root_node.firstChild.data);
這里,我們使用了responseXML提供的XMLDocument對(duì)象并用DOM方法獲取存于XML文件中的內(nèi)容.
這個(gè)名字代表了異步JavaScript+XMLHTTPRequest,并且意味著你可以在基于瀏覽器的JavaScript和服務(wù)器之間建立套接字通訊。其實(shí)AJAX并不是一種新技術(shù),而是已經(jīng)成功地用于現(xiàn)代瀏覽器中的若干成功技術(shù)的可能性組合。所有的AJAX應(yīng)用程序?qū)崿F(xiàn)了一種“豐富的”UI——這是通過(guò)JavaScript操作HTML文檔對(duì)象模型并且經(jīng)由XMLHttpRequest實(shí)現(xiàn)的精確定位的數(shù)據(jù)檢索來(lái)實(shí)現(xiàn)的。典型的示例AJAX應(yīng)用程序是Google Labs(http://labs.google.com)的Google Maps和Google Suggest。這些應(yīng)用程序現(xiàn)場(chǎng)監(jiān)視用戶(hù)輸入并且提供實(shí)時(shí)的頁(yè)面更新。最重要的是,在用戶(hù)通過(guò)地圖導(dǎo)航或輸入一個(gè)查找字符串的同時(shí),這些事件不需要刷新頁(yè)面。
事實(shí)上,支持這些令人感到驚訝的應(yīng)用的技術(shù)已經(jīng)出現(xiàn)一段時(shí)間了,盡管它們要求復(fù)雜的技能以及使用瀏覽器的技巧。一些專(zhuān)利產(chǎn)品就提供了相似的能力——如Macromedia Flash插件,Java Applets或.NET運(yùn)行時(shí)——在達(dá)到實(shí)用上已經(jīng)有一段時(shí)間了。把一種可與服務(wù)器通話的腳本組件引入到瀏覽器中的思想早在IE 5.0中就已經(jīng)存在。Firefox和其它流行的瀏覽器也加入到瀏覽器大軍中并以一種內(nèi)置對(duì)象形式支持XMLHTTPRequest。隨著跨平臺(tái)瀏覽器的出現(xiàn),這些技術(shù)得到了認(rèn)可并在2004年3月一家稱(chēng)為Adaptive Path的公司中正式提出了AJAX。
簡(jiǎn)而言之,由于來(lái)自于Google的支持和安裝了一點(diǎn)可用的瀏覽器技術(shù),加上為了一種"更好的用戶(hù)體驗(yàn)",每個(gè)人都在把客戶(hù)端技術(shù)添加到Web應(yīng)用程序上。
二. AJAX與傳統(tǒng)應(yīng)用程序的區(qū)別
一個(gè)傳統(tǒng)Web應(yīng)用程序模型實(shí)際上是一種基本的事件——用戶(hù)被迫提交表單以實(shí)現(xiàn)頁(yè)面交換。也就是說(shuō),表單提交和頁(yè)面?zhèn)魉蜔o(wú)法得到保證:還有更壞的情形——用戶(hù)需要再次點(diǎn)擊。這與AJAX截然不同-——數(shù)據(jù)跨過(guò)線路而不是完整的HTML頁(yè)面?zhèn)鬏敗_@種數(shù)據(jù)交換是經(jīng)由特定的瀏覽器對(duì)象:XMLHttpRequest實(shí)現(xiàn)的;再由適當(dāng)?shù)倪壿媮?lái)處理每個(gè)數(shù)據(jù)請(qǐng)求的結(jié)果,頁(yè)面的特定區(qū)域而不是完整的頁(yè)面被更新。結(jié)果是更快的速度,更少的擁擠和更好的信息傳送控制。
傳統(tǒng)型"click-refresh"Web應(yīng)用程序強(qiáng)迫用戶(hù)中斷工作過(guò)程而等待頁(yè)面的重裝。通過(guò)引入AJAX技術(shù),一個(gè)客戶(hù)端腳本能夠異步地與服務(wù)器通話,而用戶(hù)仍能保持輸入數(shù)據(jù)。除了對(duì)用戶(hù)透明之外,這樣的異步意味著服務(wù)器可以有更多時(shí)間來(lái)處理請(qǐng)求。
傳統(tǒng)Web應(yīng)用程序把所有的處理代理到服務(wù)器并且強(qiáng)迫服務(wù)器進(jìn)行狀態(tài)管理。AJAX允許靈活劃分應(yīng)用程序邏輯以及客戶(hù)和服務(wù)器之間的狀態(tài)管理。這就消除了一種"click-refresh"依賴(lài)性并且提供更好的服務(wù)器可伸縮性。當(dāng)該狀態(tài)存儲(chǔ)在客戶(hù)端,你就不必跨越服務(wù)器來(lái)維持會(huì)話或保存/結(jié)束狀態(tài)-其使用期限是由客戶(hù)端來(lái)定義的。
三. AJAX——分布式的MVC
盡管AJAX應(yīng)用程序依靠JavaScript來(lái)實(shí)現(xiàn)描述層,然而處理能力和知識(shí)庫(kù)仍然存在于服務(wù)器上。此時(shí),AJAX應(yīng)用程序大量的與J2EE服務(wù)器通訊——把數(shù)據(jù)輸入/輸出Web服務(wù)和servlets。具有基于AJAX的描述層的J2EE應(yīng)用程序和標(biāo)準(zhǔn)J2EE應(yīng)用程序之間的區(qū)別首先在于,MVC是通過(guò)線路分布的。通過(guò)使用AJAX,視圖是本地的,而模型和控制器是分布式的——這使得開(kāi)發(fā)者能夠靈活地決定哪些部件會(huì)是基于客戶(hù)端的。具體地說(shuō),本地視圖通過(guò)巧妙地操作HTML DOM而生成圖形;控制器局部地處理用戶(hù)輸入并且根據(jù)開(kāi)發(fā)者的判斷擴(kuò)展到服務(wù)器的處理——經(jīng)由HTTP請(qǐng)求(Web服務(wù),XML/RPC或其它)實(shí)現(xiàn);模型的遠(yuǎn)程部分是根據(jù)客戶(hù)端需要而下載的以達(dá)到實(shí)時(shí)更新客戶(hù)端頁(yè)面;并且狀態(tài)是在客戶(hù)端收集的。
在以后的AJAX文章中,我們將比較深入地討論這里的每一種組件并提供有關(guān)它們聯(lián)合在一起進(jìn)行應(yīng)用的示例。現(xiàn)在,先不多說(shuō),讓我們?cè)敿?xì)地分析一個(gè)簡(jiǎn)單的AJAX示例。
四. 郵政區(qū)號(hào)校驗(yàn)和查詢(xún)
我們將創(chuàng)建一個(gè)包含三個(gè)INPUT字段(Zip,City和State)的HTML頁(yè)面。我們將保證,只要用戶(hù)輸入郵政區(qū)號(hào)的前三個(gè)數(shù)字,該頁(yè)面上的字段就會(huì)用第一個(gè)匹配的狀態(tài)值填充。一旦用戶(hù)輸入了所有五位郵政區(qū)號(hào)數(shù),我們將立即決定和填充相應(yīng)的城市。如果郵政區(qū)號(hào)無(wú)效(在服務(wù)器的數(shù)據(jù)庫(kù)沒(méi)有找到),那么我們將把郵政區(qū)號(hào)的邊界設(shè)置為紅色。這樣的可視化線索有助于用戶(hù)并且在現(xiàn)代瀏覽器中已經(jīng)成為一種標(biāo)準(zhǔn)(作為一實(shí)例,當(dāng)Firefox找到一個(gè)HTML頁(yè)面中的匹配關(guān)鍵字時(shí),它會(huì)高亮與你在瀏覽器查找域輸入的內(nèi)容一致的部分)。
讓我們首先創(chuàng)建一個(gè)簡(jiǎn)單的包含三個(gè)INPUT字段的HTML:zip,city和state。請(qǐng)注意,一旦一個(gè)字符輸入進(jìn)郵政區(qū)號(hào)字段域中,即調(diào)用方法zipChanged()。JavaScript函數(shù)zipChanged()(見(jiàn)下)在當(dāng)zip長(zhǎng)度為3時(shí)調(diào)用函數(shù)updateState(),而在當(dāng)zip長(zhǎng)度為5時(shí)調(diào)用函數(shù)up-dateCity()。而updateCity()和updateState()把大部分的工作代理到另一個(gè)函數(shù)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>
??? 函數(shù)ask()與服務(wù)器進(jìn)行通訊并分配一個(gè)回調(diào)函數(shù)來(lái)處理服務(wù)器的響應(yīng)(見(jiàn)下列代碼)。后面,我們將分析具有雙重特點(diǎn)的resolveZip.jsp的內(nèi)容-它根據(jù)zip字段中的字符數(shù)查找city或state信息。重要的是,ask()使用了具有異步特點(diǎn)的XmlHttpRequest,這樣填充state和city字段或著色zip字段邊界就可以不必減慢數(shù)據(jù)入口而得以實(shí)現(xiàn)。首先,我們調(diào)用request.open()-它用服務(wù)器打開(kāi)套接字頻道,使用一個(gè)HTTP動(dòng)詞(GET或POST)作為第一個(gè)參數(shù)并且以數(shù)據(jù)提供者的URL作為第二個(gè)參數(shù)。request.open()的最后一個(gè)參數(shù)被設(shè)置為true-它指示該請(qǐng)求的異步特性。注意,該請(qǐng)求還沒(méi)有被提交。隨著對(duì)request.send()的調(diào)用,開(kāi)始提交-這可以為POST提供任何必要的有效載荷。在使用異步請(qǐng)求時(shí),我們必須使用request.onreadystatechanged屬性來(lái)分配請(qǐng)求的回調(diào)函數(shù)。(如果請(qǐng)求是同步的話,我們應(yīng)該能夠在調(diào)用request.send之后立即處理結(jié)果,但是我們也有可能阻斷用戶(hù),直到該請(qǐng)求完成為止。)
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()函數(shù)(見(jiàn)上)是一跨瀏覽器的XMLHTTPRequest的一個(gè)實(shí)例的構(gòu)造器;稍后我們將分析它。到目前為止,請(qǐng)注意對(duì)于handleResponse()的調(diào)用是如何用一匿名函數(shù)包裝的-這個(gè)函數(shù)是function(){handleHttpResponse(http,fieldToFill, lookupField)}。
該函數(shù)的代碼是動(dòng)態(tài)創(chuàng)建的并且在每次我們給http.onreadstatechange屬性賦值時(shí)被編譯。結(jié)果,JavaScript創(chuàng)建一個(gè)指向上下文(所有的變量都可以存取正在結(jié)束的方法-ask())的指針。這樣以來(lái),匿名函數(shù)和handleResponse()就能夠被保證充分存取所有的上下文宿主的變量,直至到匿名函數(shù)的參考被垃圾回收站收集為止。換句話說(shuō),無(wú)論何時(shí)我們的匿名函數(shù)被調(diào)用,它都能無(wú)縫地參考request,fieldToFill和lookupField變量,就象它們是全局的一樣。而且,每次ask()調(diào)用都將創(chuàng)建環(huán)境的一個(gè)獨(dú)立拷貝,并且此時(shí)這些變量中保存有該函數(shù)將結(jié)束時(shí)的值。
現(xiàn)在,讓我們分析一下函數(shù)handleResponse()。既然它能夠在請(qǐng)求處理的不同狀態(tài)下激活,那么該函數(shù)將忽略所有的情形-除了該請(qǐng)求處理完成之外-這相應(yīng)于request.readyState屬性等于4("Completed")。此時(shí),該函數(shù)讀取服務(wù)器的響應(yīng)文本。與它的名字所暗示的相反,XmlHttpRequest的輸入和輸出都不必限于XML格式。特別地,我們的resolveZip.jsp(見(jiàn)源碼中的列表1)返回普通文本。如果返回值為"unknown",那么該函數(shù)將假定郵政區(qū)號(hào)是無(wú)效的并且把查找字段(zip)邊界顏色置為紅色。否則,返回值被用于填充字段state或city,并且zip的邊界被賦予一種缺省顏色。
XMLHttpRequest-傳輸對(duì)象
讓我們返回到我們的XMLHTTPRequest的跨瀏覽器實(shí)現(xiàn)。最后一個(gè)列表包含一個(gè)HttpRequest()函數(shù)-它向上兼容于IE5.0和Mozilla 1.8/FireFox。為簡(jiǎn)化起見(jiàn),我們只創(chuàng)建一個(gè)微軟XMLHTTPRequest對(duì)象,而且如果創(chuàng)建失敗,我們假定它是Firefox/Mozilla。
該函數(shù)的核心是XMLHTTPRequest-這是一個(gè)本機(jī)瀏覽器對(duì)象,它為包括HTTP協(xié)議的任何東西與服務(wù)器之間的通訊提供方便。它允許指定任何HTTP動(dòng)詞,頭部和有效載荷,并且能夠以異步或同步方式工作。不需要下載也不需要安裝任何插件-盡管在IE的情形下,XMLHTTPRequest是一個(gè)集成到瀏覽器內(nèi)部的ActiveX。因而,"Run ActiveX Control and Plugins"默認(rèn)IE權(quán)限應(yīng)該正好適合使用它。
最重要的是,XMLHTTPRequest允許一個(gè)到服務(wù)器的RPC風(fēng)格的編程查詢(xún)而不需要任何頁(yè)面刷新。它以一種可預(yù)測(cè)的,可控制的方式來(lái)實(shí)現(xiàn)此-提供了到HTTP協(xié)議的所有細(xì)節(jié)的完整存取-包括頭部和數(shù)據(jù)的任何定制格式。在以后的文章中,我們將向你展示其它一些業(yè)界協(xié)議-你可以在這些傳輸協(xié)議(如Web服務(wù)和XML-RPC)之上運(yùn)行-它們極大地簡(jiǎn)化大規(guī)模應(yīng)用程序的開(kāi)發(fā)和維護(hù)。
五.服務(wù)器端邏輯
最后,服務(wù)器端的resolveZip.jsp被從函數(shù)ask()中調(diào)用(見(jiàn)所附源碼中的列表1)。這個(gè)resolveZip.jsp在兩種由當(dāng)前的郵政區(qū)號(hào)長(zhǎng)度所區(qū)分的獨(dú)立的場(chǎng)所下被調(diào)用(見(jiàn)zipChanged()函數(shù))。請(qǐng)求參數(shù)lookupType的值或者是state或者是city。為簡(jiǎn)化起見(jiàn),我們將假定,兩個(gè)文件state.properties和city.properties都位于服務(wù)器中C驅(qū)動(dòng)器的根目錄下。resolveZip.jsp邏輯負(fù)責(zé)用適當(dāng)?shù)念A(yù)裝載的文件返回查找值。
我們的支持AJAX的頁(yè)面現(xiàn)在已經(jīng)準(zhǔn)備好了。
六.遠(yuǎn)程腳本技術(shù)-一種可選方法
一些更舊的AJAX實(shí)現(xiàn)是基于所謂的遠(yuǎn)程腳本技術(shù)。這種思想是,用戶(hù)的行為導(dǎo)致經(jīng)由IFRAME對(duì)服務(wù)器進(jìn)行查詢(xún),而服務(wù)器用JavaScript作出響應(yīng),該腳本一旦到達(dá)客戶(hù)端立即被執(zhí)行。這與XMLHttpRequest方法相比存在較大的區(qū)別,在后者情況下,服務(wù)器響應(yīng)數(shù)據(jù)而客戶(hù)端解釋數(shù)據(jù)。其好處是這種解決方案支持更舊的瀏覽器。
基于IFRAME示例的HTML部分(見(jiàn)所附源碼中的列表2)與我們?cè)赬MLHTTPRequest場(chǎng)合下所用的極相似,但是這次我們將引入另外一個(gè)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>
我們保持每次擊鍵都調(diào)用zipChanged()一次,但是這一次,從zipChanged()中被調(diào)用的函數(shù)ask()(見(jiàn)所附源碼中的列表3)負(fù)責(zé)設(shè)置IFRAME的src屬性,而不是調(diào)用一個(gè)XMLHTTPRequest:
function ask(url, fieldToFill, lookupField){
var controller = document.getElementById("controller");
controller.src= url+"&field="+fieldToFill.id+"&zip="+lookupField.id;
}
服務(wù)器端邏輯由一個(gè)粗略的resolveZip.jsp(見(jiàn)所附源碼中的列表4)所描述。它與它的XMLHTTPRequest對(duì)應(yīng)物相區(qū)別-它返回JavaScript語(yǔ)句,這些語(yǔ)句設(shè)置變量字段lookup和city的全局值,而且一旦它到達(dá)瀏覽器即從全局窗口的執(zhí)行上下文中調(diào)用函數(shù)response()。
函數(shù)response()是一修改版本的handleResponse()-這一函數(shù)可以免于處理未完成的請(qǐng)求(詳見(jiàn)本文所附源碼中的列表2)。
七. 難題
為簡(jiǎn)化起見(jiàn),讓我們"俯看"一下在我們的示例代碼中的一些重要的問(wèn)題:
1.事實(shí)-XMLHTTPRequest對(duì)象實(shí)例和回調(diào)函數(shù)調(diào)用在被使用以后并沒(méi)被破壞-在每次調(diào)用后這有可能導(dǎo)致內(nèi)存泄漏。適當(dāng)編寫(xiě)的代碼應(yīng)該破壞或重用對(duì)象池中的這些實(shí)例。而且,客戶(hù)端必須使用與服務(wù)器軟件相同的對(duì)象管理技術(shù)。
2.在大多數(shù)情況下,錯(cuò)誤往往得不到有效處理。例如,在方法ask()中對(duì)request.open()的調(diào)用可能引發(fā)一個(gè)異常,這是必須要捕獲和處理的,即使在瀏覽器中沒(méi)有設(shè)置JavaScript異常自動(dòng)捕獲功能。而handleResponse()函數(shù)又是另外一個(gè)例子。它必須要為可能的服務(wù)器端和通訊錯(cuò)誤而檢查headers和responseText值。在發(fā)生錯(cuò)誤的情況下,它必須盡力恢復(fù)并/或者報(bào)告錯(cuò)誤。正確開(kāi)發(fā)的AJAX應(yīng)用程序要盡可能避免"提交"松散的數(shù)據(jù),因?yàn)橥嬖诰€路斷開(kāi)和其它低級(jí)通訊的問(wèn)題-所以這些程序必須建立一個(gè)強(qiáng)壯的和自恢復(fù)的框架為此提供支持。
3.當(dāng)前服務(wù)器端框架提供相當(dāng)多的功能-它們可以與一種自由刷新方法和諧相處。例如,讓我們考慮一個(gè)定制的在指定時(shí)間內(nèi)的服務(wù)器端認(rèn)證的問(wèn)題。在這種情況下,我們必須攔截到XMLHTTPRequest調(diào)用的安全系統(tǒng)響應(yīng),顯示登錄屏幕,然后在用戶(hù)被認(rèn)證后重新發(fā)出請(qǐng)求。
所有的這些問(wèn)題只是一些典型的用低級(jí)API工作的任何應(yīng)用程序代碼,而且所有這些問(wèn)題都能被解決。好消息是,解決這些問(wèn)題所需要的技術(shù)十分相似于大多數(shù)Java開(kāi)發(fā)技術(shù),如Web服務(wù),定制標(biāo)簽和XML/XSLT。唯一的區(qū)別在于,現(xiàn)在這些技術(shù)以下列形式用于客戶(hù)端:
·Web服務(wù)-使用SOAP/REST/RPC等簡(jiǎn)單通訊標(biāo)準(zhǔn)
·客戶(hù)端定制標(biāo)簽-打包豐富的客戶(hù)端控件并集成AJAX功能
·數(shù)據(jù)操作-基于XML和基于XSLT技術(shù)
八. 小結(jié)
AJAX方法能夠向人們提供一種與桌面應(yīng)用程序相同的豐富的互聯(lián)網(wǎng)體驗(yàn)。但是,我們必須有選擇地使用AJAX技術(shù),如當(dāng)你仍在線購(gòu)物時(shí),你絕對(duì)不想讓你的信用卡通過(guò)后臺(tái)處理就悄悄地開(kāi)始付款。AJAX會(huì)成為一種持續(xù)的動(dòng)力嗎?我們當(dāng)然希望這樣。在過(guò)去的五年時(shí)間內(nèi)我們一直在努力開(kāi)發(fā)AJAX應(yīng)用程序并且能證明它是健全并且很有效的。然而,它要求一個(gè)開(kāi)發(fā)者必須精通大量技術(shù)而不是在傳統(tǒng)的"click-refresh"Web應(yīng)用程序中所使用的那些。
<html>
<body>
<script type="text/vbscript">
set xmlDoc=CreateObject("Microsoft.XMLDOM")
xmlDoc.async="false"
xmlDoc.load("note.xml")
for each x in xmlDoc.documentElement.childNodes
?document.write(x.childnodes(0).nodeValue & "<br />")
next
</script>
</body>
</html
準(zhǔn)備一個(gè)note.xml文件,很簡(jiǎn)單幾行:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited with XML Spy v4.2 -->
<note time="12:03:46">
? <to>Tove</to>
? <from>Jani</from>
? <heading>Reminder</heading>
? <body>Don't forget me this weekend!</body>
</note>
示例二:創(chuàng)建一個(gè)XMLHttpRequest.
<html>
<head>
<script type="text/javascript">
var xmlhttp
function loadXMLDoc(url)
{
// code for Mozilla, etc.
if (window.XMLHttpRequest)
? {
? xmlhttp=new XMLHttpRequest()
? xmlhttp.onreadystatechange=state_Change
? xmlhttp.open("GET",url,true)
? xmlhttp.send(null)
? }
// code for IE
else if (window.ActiveXObject)
? {
? xmlhttp=new ActiveXObject("Microsoft.XMLHTTP")
??? if (xmlhttp)
??? {
??? xmlhttp.onreadystatechange=state_Change
??? xmlhttp.open("GET",url,true)
??? xmlhttp.send()
??? }
? }
}
function state_Change()
{
// if xmlhttp shows "loaded"
if (xmlhttp.readyState==4)
? {
? // if "OK"
? if (xmlhttp.status==200)
? {
? alert("XML data OK")
? document.getElementById('A1').innerHTML=xmlhttp.status
? document.getElementById('A2').innerHTML=xmlhttp.statusText
? document.getElementById('A3').innerHTML=xmlhttp.responseText
? }
? else
? {
? alert("Problem retrieving XML data:" + xmlhttp.statusText)
? }
? }
}
</script>
</head>
<body script_onload="loadXMLDoc('note.xml')">
<h2>Using the HttpRequest Object</h2>
<p><b>status:</b>
<span id="A1"></span>
</p>
<p><b>status text:</b>
<span id="A2"></span>
</p>
<p><b>response:</b>
<br><span id="A3"></span>
</p>
</body>
</html>
對(duì)著代碼 和運(yùn)行結(jié)果看,很容易熟悉XMLHttpRequest對(duì)象.
function loadXMLDoc(url)中根據(jù)不同的客戶(hù)端創(chuàng)建XMLHttpRequest對(duì)象,然后對(duì)傳入的url向Server發(fā)送GET請(qǐng)求,true參數(shù)是為了表明要異步請(qǐng)求.完了之后 ,state_Change()被調(diào)用,該方法就是得到Server返回的狀態(tài).并傳給網(wǎng)頁(yè)顯示出來(lái).
但是,ajax 不僅僅是一種時(shí)尚,它是一種構(gòu)建網(wǎng)站的強(qiáng)大方法,而且不像學(xué)習(xí)一種全新的語(yǔ)言那樣困難。
但在詳細(xì)探討 ajax 是什么之前,先讓我們花幾分鐘了解 ajax 做什么。目前,編寫(xiě)應(yīng)用程序時(shí)有兩種基本的選擇:
桌面應(yīng)用程序,web 應(yīng)用程序。兩者是類(lèi)似的,桌面應(yīng)用程序通常以 cd 為介質(zhì)(有時(shí)候可從網(wǎng)站下載)并完全安裝到您的計(jì)算機(jī)上。桌面應(yīng)用程序可能使用互聯(lián)網(wǎng)下載更新,但運(yùn)行這些應(yīng)用程序的代碼在桌面計(jì)算機(jī)上。web 應(yīng)用程序運(yùn)行在某處的 web 服務(wù)器上 —— 毫不奇怪,要通過(guò) web 瀏覽器訪問(wèn)這種應(yīng)用程序。
不過(guò),比這些應(yīng)用程序的運(yùn)行代碼放在何處更重要的是,應(yīng)用程序如何運(yùn)轉(zhuǎn)以及如何與其進(jìn)行交互。桌面應(yīng)用程序一般很快(就在您的計(jì)算機(jī)上運(yùn)行,不用等待互聯(lián)網(wǎng)連接),具有漂亮的用戶(hù)界面(通常和操作系統(tǒng)有關(guān))和非凡的動(dòng)態(tài)性。可以單擊、選擇、輸入、打開(kāi)菜單和子菜單、到處巡游,基本上不需要等待。
另一方面,web 應(yīng)用程序是最新的潮流,它們提供了在桌面上不能實(shí)現(xiàn)的服務(wù)(比如 amazon.com 和 ebay)。但是,伴隨著 web 的強(qiáng)大而出現(xiàn)的是等待,等待服務(wù)器響應(yīng),等待屏幕刷新,等待請(qǐng)求返回和生成新的頁(yè)面。
顯然這樣說(shuō)過(guò)于簡(jiǎn)略了,但基本的概念就是如此。您可能已經(jīng)猜到,ajax 嘗試建立桌面應(yīng)用程序的功能和交互性,與不斷更新的 web 應(yīng)用程序之間的橋梁。可以使用像桌面應(yīng)用程序中常見(jiàn)的動(dòng)態(tài)用戶(hù)界面和漂亮的控件,不過(guò)是在 web 應(yīng)用程序中。
還等什么呢?我們來(lái)看看 ajax 如何將笨拙的 web 界面轉(zhuǎn)化成能迅速響應(yīng)的 ajax 應(yīng)用程序吧。
老技術(shù),新技巧
在談到 ajax 時(shí),實(shí)際上涉及到多種技術(shù),要靈活地運(yùn)用它必須深入了解這些不同的技術(shù)(本系列的頭幾篇文章將分別討論這些技術(shù))。好消息是您可能已經(jīng)非常熟悉其中的大部分技術(shù),更好的是這些技術(shù)都很容易學(xué)習(xí),并不像完整的編程語(yǔ)言(如 java 或 ruby)那樣困難。
ajax 的定義
順便說(shuō)一下,ajax 是 asynchronous javascript and xml(以及 dhtml 等)的縮寫(xiě)。這個(gè)短語(yǔ)是 adaptive path 的 jesse james garrett 發(fā)明的,按照 jesse 的解釋?zhuān)@不是個(gè)首字母縮寫(xiě)詞。
下面是 ajax 應(yīng)用程序所用到的基本技術(shù):
html 用于建立 web 表單并確定應(yīng)用程序其他部分使用的字段。
javascript 代碼是運(yùn)行 ajax 應(yīng)用程序的核心代碼,幫助改進(jìn)與服務(wù)器應(yīng)用程序的通信。
dhtml 或 dynamic html,用于動(dòng)態(tài)更新表單。我們將使用div、span和其他動(dòng)態(tài) html 元素來(lái)標(biāo)記 html。
文檔對(duì)象模型 dom 用于(通過(guò) javascript 代碼)處理 html 結(jié)構(gòu)和(某些情況下)服務(wù)器返回的 xml。
我們來(lái)進(jìn)一步分析這些技術(shù)的職責(zé)。以后的文章中我將深入討論這些技術(shù),目前只要熟悉這些組件和技術(shù)就可以了。對(duì)這些代碼越熟悉,就越容易從對(duì)這些技術(shù)的零散了解轉(zhuǎn)變到真正把握這些技術(shù)(同時(shí)也真正打開(kāi)了 web 應(yīng)用程序開(kāi)發(fā)的大門(mén))。
xmlhttprequest 對(duì)象
要了解的一個(gè)對(duì)象可能對(duì)您來(lái)說(shuō)也是最陌生的,即xmlhttprequest。這是一個(gè) javascript 對(duì)象,創(chuàng)建該對(duì)象很簡(jiǎn)單,如清單 1 所示。
清單 1. 創(chuàng)建新的 xmlhttprequest 對(duì)象
<script language="javascript" type="text/javascript">
var xmlhttp = new xmlhttprequest();
</script>
下一期文章中將進(jìn)一步討論這個(gè)對(duì)象,現(xiàn)在要知道這是處理所有服務(wù)器通信的對(duì)象。繼續(xù)閱讀之前,先停下來(lái)想一想:通過(guò)xmlhttprequest對(duì)象與服務(wù)器進(jìn)行對(duì)話的是 javascript 技術(shù)。這不是一般的應(yīng)用程序流,這恰恰是 ajax 的強(qiáng)大功能的來(lái)源。
在一般的 web 應(yīng)用程序中,用戶(hù)填寫(xiě)表單字段并單擊 submit 按鈕。然后整個(gè)表單發(fā)送到服務(wù)器,服務(wù)器將它轉(zhuǎn)發(fā)給處理表單的腳本(通常是 php 或 java,也可能是 cgi 進(jìn)程或者類(lèi)似的東西),腳本執(zhí)行完成后再發(fā)送回全新的頁(yè)面。該頁(yè)面可能是帶有已經(jīng)填充某些數(shù)據(jù)的新表單的 html,也可能是確認(rèn)頁(yè)面,或者是具有根據(jù)原來(lái)表單中輸入數(shù)據(jù)選擇的某些選項(xiàng)的頁(yè)面。當(dāng)然,在服務(wù)器上的腳本或程序處理和返回新表單時(shí)用戶(hù)必須等待。屏幕變成一片空白,等到服務(wù)器返回?cái)?shù)據(jù)后再重新繪制。這就是交互性差的原因,用戶(hù)得不到立即反饋,因此感覺(jué)不同于桌面應(yīng)用程序。
ajax 基本上就是把 javascript 技術(shù)和xmlhttprequest對(duì)象放在 web 表單和服務(wù)器之間。當(dāng)用戶(hù)填寫(xiě)表單時(shí),數(shù)據(jù)發(fā)送給一些 javascript 代碼而不是直接發(fā)送給服務(wù)器。相反,javascript 代碼捕獲表單數(shù)據(jù)并向服務(wù)器發(fā)送請(qǐng)求。同時(shí)用戶(hù)屏幕上的表單也不會(huì)閃爍、消失或延遲。換句話說(shuō),javascript 代碼在幕后發(fā)送請(qǐng)求,用戶(hù)甚至不知道請(qǐng)求的發(fā)出。更好的是,請(qǐng)求是異步發(fā)送的,就是說(shuō) javascript 代碼(和用戶(hù))不用等待服務(wù)器的響應(yīng)。因此用戶(hù)可以繼續(xù)輸入數(shù)據(jù)、滾動(dòng)屏幕和使用應(yīng)用程序。
然后,服務(wù)器將數(shù)據(jù)返回 javascript 代碼(仍然在 web 表單中),后者決定如何處理這些數(shù)據(jù)。它可以迅速更新表單數(shù)據(jù),讓人感覺(jué)應(yīng)用程序是立即完成的,表單沒(méi)有提交或刷新而用戶(hù)得到了新數(shù)據(jù)。javascript 代碼甚至可以對(duì)收到的數(shù)據(jù)執(zhí)行某種計(jì)算,再發(fā)送另一個(gè)請(qǐng)求,完全不需要用戶(hù)干預(yù)!這就是xmlhttprequest的強(qiáng)大之處。它可以根據(jù)需要自行與服務(wù)器進(jìn)行交互,用戶(hù)甚至可以完全不知道幕后發(fā)生的一切。結(jié)果就是類(lèi)似于桌面應(yīng)用程序的動(dòng)態(tài)、快速響應(yīng)、高交互性的體驗(yàn),但是背后又擁有互聯(lián)網(wǎng)的全部強(qiáng)大力量。
加入一些 javascript
得到xmlhttprequest的句柄后,其他的 javascript 代碼就非常簡(jiǎn)單了。事實(shí)上,我們將使用 javascript 代碼完成非常基本的任務(wù):
獲取表單數(shù)據(jù):javascript 代碼很容易從 html 表單中抽取數(shù)據(jù)并發(fā)送到服務(wù)器。
修改表單上的數(shù)據(jù):更新表單也很簡(jiǎn)單,從設(shè)置字段值到迅速替換圖像。
解析 html 和 xml:使用 javascript 代碼操縱 dom(請(qǐng)參閱 下一節(jié)),處理 html 表單服務(wù)器返回的 xml 數(shù)據(jù)的結(jié)構(gòu)。
對(duì)于前兩點(diǎn),需要非常熟悉getelementbyid()方法,如清單 2 所示。
清單 2. 用 javascript 代碼捕獲和設(shè)置字段值
/ 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];
這里沒(méi)有特別需要注意的地方,真是好極了!您應(yīng)該認(rèn)識(shí)到這里并沒(méi)有非常復(fù)雜的東西。只要掌握了xmlhttprequest,ajax 應(yīng)用程序的其他部分就是如清單 2 所示的簡(jiǎn)單 javascript 代碼了,混合有少量的 html。同時(shí),還要用一點(diǎn)兒 dom,我們就來(lái)看看吧。
以 dom 結(jié)束
最后還有 dom,即文檔對(duì)象模型。可能對(duì)有些讀者來(lái)說(shuō) dom 有點(diǎn)兒令人生畏,html 設(shè)計(jì)者很少使用它,即使 javascript 程序員也不大用到它,除非要完成某項(xiàng)高端編程任務(wù)。大量使用 dom 的是復(fù)雜的 java 和 c/c++ 程序,這可能就是 dom 被認(rèn)為難以學(xué)習(xí)的原因。
幸運(yùn)的是,在 javascript 技術(shù)中使用 dom 很容易,也非常直觀。現(xiàn)在,按照常規(guī)也許應(yīng)該說(shuō)明如何使用 dom,或者至少要給出一些示例代碼,但這樣做也可能誤導(dǎo)您。即使不理會(huì) dom,仍然能深入地探討 ajax,這也是我準(zhǔn)備采用的方法。以后的文章將再次討論 dom,現(xiàn)在只要知道可能需要 dom 就可以了。當(dāng)需要在 javascript 代碼和服務(wù)器之間傳遞 xml 和改變 html 表單的時(shí)候,我們?cè)偕钊胙芯?dom。沒(méi)有它也能做一些有趣的工作,因此現(xiàn)在就把 dom 放到一邊吧。
獲取 request 對(duì)象
有了上面的基礎(chǔ)知識(shí)后,我們來(lái)看看一些具體的例子。xmlhttprequest是 ajax 應(yīng)用程序的核心,而且對(duì)很多讀者來(lái)說(shuō)可能還比較陌生,我們就從這里開(kāi)始吧。從清單 1 可以看出,創(chuàng)建和使用這個(gè)對(duì)象非常簡(jiǎn)單,不是嗎?等一等。
還記得幾年前的那些討厭的瀏覽器戰(zhàn)爭(zhēng)嗎?沒(méi)有一樣?xùn)|西在不同的瀏覽器上得到同樣的結(jié)果。不管您是否相信,這些戰(zhàn)爭(zhēng)仍然在繼續(xù),雖然規(guī)模較小。但令人奇怪的是,xmlhttprequest成了這場(chǎng)戰(zhàn)爭(zhēng)的犧牲品之一。因此獲得xmlhttprequest對(duì)象可能需要采用不同的方法。下面我將詳細(xì)地進(jìn)行解釋。
使用 microsoft 瀏覽器
microsoft 瀏覽器 internet explorer 使用 msxml 解析器處理 xml(可以通過(guò)參考資料進(jìn)一步了解 msxml)。因此如果編寫(xiě)的 ajax 應(yīng)用程序要和 internet explorer 打交道,那么必須用一種特殊的方式創(chuàng)建對(duì)象。
但并不是這么簡(jiǎn)單。根據(jù) internet explorer 中安裝的 javascript 技術(shù)版本不同,msxml 實(shí)際上有兩種不同的版本,因此必須對(duì)這兩種情況分別編寫(xiě)代碼。請(qǐng)參閱清單 3,其中的代碼在 microsoft 瀏覽器上創(chuàng)建了一個(gè)xmlhttprequest。
清單 3. 在 microsoft 瀏覽器上創(chuàng)建 xmlhttprequest 對(duì)象
var xmlhttp = false;
try {
?xmlhttp = new activexobject("msxml2.xmlhttp");
} catch (e) {
?try {
??? xmlhttp = new activexobject("microsoft.xmlhttp");
?} catch (e2) {
??? xmlhttp = false;
?}
}
您對(duì)這些代碼可能還不完全理解,但沒(méi)有關(guān)系。當(dāng)本系列文章結(jié)束的時(shí)候,您將對(duì) javascript 編程、錯(cuò)誤處理、條件編譯等有更深的了解。現(xiàn)在只要牢牢記住其中的兩行代碼:
xmlhttp = new activexobject("msxml2.xmlhttp");
和
xmlhttp = new activexobject("microsoft.xmlhttp");。
這兩行代碼基本上就是嘗試使用一個(gè)版本的 msxml 創(chuàng)建對(duì)象,如果失敗則使用另一個(gè)版本創(chuàng)建該對(duì)象。不錯(cuò)吧?如果都不成功,則將xmlhttp變量設(shè)為 false,告訴您的代碼出現(xiàn)了問(wèn)題。如果出現(xiàn)這種情況,可能是因?yàn)榘惭b了非 microsoft 瀏覽器,需要使用不同的代碼。
處理 mozilla 和非 microsoft 瀏覽器
如果選擇的瀏覽器不是 internet explorer,或者為非 microsoft 瀏覽器編寫(xiě)代碼,就需要使用不同的代碼。事實(shí)上就是清單 1 所示的一行簡(jiǎn)單代碼:
var xmlhttp = new xmlhttprequest object;。
這行簡(jiǎn)單得多的代碼在 mozilla、firefox、safari、opera 以及基本上所有以任何形式或方式支持 ajax 的非 microsoft 瀏覽器中,創(chuàng)建了xmlhttprequest對(duì)象。
結(jié)合起來(lái)
關(guān)鍵是要支持所有瀏覽器。誰(shuí)愿意編寫(xiě)一個(gè)只能用于 internet explorer 或者非 microsoft 瀏覽器的應(yīng)用程序呢?或者更糟,要編寫(xiě)一個(gè)應(yīng)用程序兩次?當(dāng)然不!因此代碼要同時(shí)支持 internet explorer 和非 microsoft 瀏覽器。清單 4 顯示了這樣的代碼。
清單 4. 以支持多種瀏覽器的方式創(chuàng)建 xmlhttprequest 對(duì)象
/* 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();
}
現(xiàn)在先不管那些注釋掉的奇怪符號(hào),如@cc_on,這是特殊的 javascript 編譯器命令,將在下一期針對(duì)xmlhttprequest的文章中詳細(xì)討論。這段代碼的核心分為三步:
1.?????? 建立一個(gè)變量xmlhttp來(lái)引用即將創(chuàng)建的xmlhttprequest對(duì)象。
2.?????? 嘗試在 microsoft 瀏覽器中創(chuàng)建該對(duì)象:
o??????? 嘗試使用msxml2.xmlhttp對(duì)象創(chuàng)建它。
o??????? 如果失敗,再?lài)L試microsoft.xmlhttp對(duì)象。
3.?????? 如果仍然沒(méi)有建立xmlhttp,則以非 microsoft 的方式創(chuàng)建該對(duì)象。
最后,xmlhttp應(yīng)該引用一個(gè)有效的xmlhttprequest對(duì)象,無(wú)論運(yùn)行什么樣的瀏覽器。
關(guān)于安全性的一點(diǎn)說(shuō)明
安全性如何呢?現(xiàn)在瀏覽器允許用戶(hù)提高他們的安全等級(jí),關(guān)閉 javascript 技術(shù),禁用瀏覽器中的任何選項(xiàng)。在這種情況下,代碼無(wú)論如何都不會(huì)工作。此時(shí)必須適當(dāng)?shù)靥幚韱?wèn)題,這需要單獨(dú)的一篇文章來(lái)討論,要放到以后了(這個(gè)系列夠長(zhǎng)了吧?不用擔(dān)心,讀完之前也許您就掌握了)。現(xiàn)在要編寫(xiě)一段健壯但不夠完美的代碼,對(duì)于掌握 ajax 來(lái)說(shuō)就很好了。以后我們還將討論更多的細(xì)節(jié)。
ajax 世界中的請(qǐng)求/響應(yīng)
現(xiàn)在我們介紹了 ajax,對(duì)xmlhttprequest對(duì)象以及如何創(chuàng)建它也有了基本的了解。如果閱讀得很仔細(xì),您可能已經(jīng)知道與服務(wù)器上的 web 應(yīng)用程序打交道的是 javascript 技術(shù),而不是直接提交給那個(gè)應(yīng)用程序的 html 表單。
還缺少什么呢?到底如何使用xmlhttprequest。因?yàn)檫@段代碼非常重要,您編寫(xiě)的每個(gè) ajax 應(yīng)用程序都要以某種形式使用它,先看看 ajax 的基本請(qǐng)求/響應(yīng)模型是什么樣吧。
發(fā)出請(qǐng)求
您已經(jīng)有了一個(gè)嶄新的xmlhttprequest對(duì)象,現(xiàn)在讓它干點(diǎn)活兒吧。首先需要一個(gè) web 頁(yè)面能夠調(diào)用的 javascript 方法(比如當(dāng)用戶(hù)輸入文本或者從菜單中選擇一項(xiàng)時(shí))。接下來(lái)就是在所有 ajax 應(yīng)用程序中基本都雷同的流程:
1.?????? 從 web 表單中獲取需要的數(shù)據(jù)。
2.?????? 建立要連接的 url。
3.?????? 打開(kāi)到服務(wù)器的連接。
4.?????? 設(shè)置服務(wù)器在完成后要運(yùn)行的函數(shù)。
5.?????? 發(fā)送請(qǐng)求。
清單 5 中的示例 ajax 方法就是按照這個(gè)順序組織的:
清單 5. 發(fā)出 ajax 請(qǐng)求
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);
}
其中大部分代碼意義都很明確。開(kāi)始的代碼使用基本 javascript 代碼獲取幾個(gè)表單字段的值。然后設(shè)置一個(gè) php 腳本作為鏈接的目標(biāo)。要注意腳本 url 的指定方式,city 和 state(來(lái)自表單)使用簡(jiǎn)單的 get 參數(shù)附加在 url 之后。
然后打開(kāi)一個(gè)連接,這是您第一次看到使用xmlhttprequest。其中指定了連接方法(get)和要連接的 url。最后一個(gè)參數(shù)如果設(shè)為true,那么將請(qǐng)求一個(gè)異步連接(這就是 ajax 的由來(lái))。如果使用false,那么代碼發(fā)出請(qǐng)求后將等待服務(wù)器返回的響應(yīng)。如果設(shè)為true,當(dāng)服務(wù)器在后臺(tái)處理請(qǐng)求的時(shí)候用戶(hù)仍然可以使用表單(甚至調(diào)用其他 javascript 方法)。
xmlhttp(要記住,這是xmlhttprequest對(duì)象實(shí)例)的onreadystatechange屬性可以告訴服務(wù)器在運(yùn)行完成后(可能要用五分鐘或者五個(gè)小時(shí))做什么。因?yàn)榇a沒(méi)有等待服務(wù)器,必須讓服務(wù)器知道怎么做以便您能作出響應(yīng)。在這個(gè)示例中,如果服務(wù)器處理完了請(qǐng)求,一個(gè)特殊的名為updatepage()的方法將被觸發(fā)。
最后,使用值null調(diào)用send()。因?yàn)橐呀?jīng)在請(qǐng)求 url 中添加了要發(fā)送給服務(wù)器的數(shù)據(jù)(city 和 state),所以請(qǐng)求中不需要發(fā)送任何數(shù)據(jù)。這樣就發(fā)出了請(qǐng)求,服務(wù)器按照您的要求工作。
如果沒(méi)有發(fā)現(xiàn)任何新鮮的東西,您應(yīng)該體會(huì)到這是多么簡(jiǎn)單明了!除了牢牢記住 ajax 的異步特性外,這些內(nèi)容都相當(dāng)簡(jiǎn)單。應(yīng)該感激 ajax 使您能夠?qū)P木帉?xiě)漂亮的應(yīng)用程序和界面,而不用擔(dān)心復(fù)雜的 http 請(qǐng)求/響應(yīng)代碼。
清單 5 中的代碼說(shuō)明了 ajax 的易用性。數(shù)據(jù)是簡(jiǎn)單的文本,可以作為請(qǐng)求 url 的一部分。用 get 而不是更復(fù)雜的 post 發(fā)送請(qǐng)求。沒(méi)有 xml 和要添加的內(nèi)容頭部,請(qǐng)求體中沒(méi)有要發(fā)送的數(shù)據(jù);換句話說(shuō),這就是 ajax 的烏托邦。
不用擔(dān)心,隨著本系列文章的展開(kāi),事情會(huì)變得越來(lái)越復(fù)雜。您將看到如何發(fā)送 post 請(qǐng)求、如何設(shè)置請(qǐng)求頭部和內(nèi)容類(lèi)型、如何在消息中編碼 xml、如何增加請(qǐng)求的安全性,可以做的工作還有很多!暫時(shí)先不用管那些難點(diǎn),掌握好基本的東西就行了,很快我們就會(huì)建立一整套的 ajax 工具庫(kù)。
處理響應(yīng)
現(xiàn)在要面對(duì)服務(wù)器的響應(yīng)了。現(xiàn)在只要知道兩點(diǎn):
什么也不要做,直到xmlhttp.readystate屬性的值等于 4。 服務(wù)器將把響應(yīng)填充到xmlhttp.responsetext屬性中。
其中的第一點(diǎn),即就緒狀態(tài),將在下一篇文章中詳細(xì)討論,您將進(jìn)一步了解 http 請(qǐng)求的階段,可能比您設(shè)想的還多。現(xiàn)在只要檢查一個(gè)特定的值(4)就可以了(下一期文章中還有更多的值要介紹)。第二點(diǎn),使用xmlhttp.responsetext屬性獲得服務(wù)器的響應(yīng),這很簡(jiǎn)單。清單 6 中的示例方法可供服務(wù)器根據(jù)清單 5 中發(fā)送的數(shù)據(jù)調(diào)用。
清單 6. 處理服務(wù)器響應(yīng)
function updatepage() {
?if (xmlhttp.readystate == 4) {
??? var response = xmlhttp.responsetext;
??? document.getelementbyid("zipcode").value = response;
?}
}
這些代碼同樣既不難也不復(fù)雜。它等待服務(wù)器調(diào)用,如果是就緒狀態(tài),則使用服務(wù)器返回的值(這里是用戶(hù)輸入的城市和州的 zip 編碼)設(shè)置另一個(gè)表單字段的值。于是包含 zip 編碼的zipcode字段突然出現(xiàn)了,而用戶(hù)沒(méi)有按任何按鈕!這就是前面所說(shuō)的桌面應(yīng)用程序的感覺(jué)。快速響應(yīng)、動(dòng)態(tài)感受等等,這些都只因?yàn)橛辛诵⌒〉囊欢?ajax 代碼。
細(xì)心的讀者可能注意到zipcode是一個(gè)普通的文本字段。一旦服務(wù)器返回 zip 編碼,updatepage()方法就用城市/州的 zip 編碼設(shè)置那個(gè)字段的值,用戶(hù)就可以改寫(xiě)該值。這樣做有兩個(gè)原因:保持例子簡(jiǎn)單,說(shuō)明有時(shí)候可能希望用戶(hù)能夠修改服務(wù)器返回的數(shù)據(jù)。要記住這兩點(diǎn),它們對(duì)于好的用戶(hù)界面設(shè)計(jì)來(lái)說(shuō)很重要。
連接 web 表單
還有什么呢?實(shí)際上沒(méi)有多少了。一個(gè) javascript 方法捕捉用戶(hù)輸入表單的信息并將其發(fā)送到服務(wù)器,另一個(gè) javascript 方法監(jiān)聽(tīng)和處理響應(yīng),并在響應(yīng)返回時(shí)設(shè)置字段的值。所有這些實(shí)際上都依賴(lài)于調(diào)用第一個(gè) javascript 方法,它啟動(dòng)了整個(gè)過(guò)程。最明顯的辦法是在 html 表單中增加一個(gè)按鈕,但這是 2001 年的辦法,您不這樣認(rèn)為嗎?還是像清單 7 這樣利用 javascript 技術(shù)吧。
清單 7. 啟動(dòng)一個(gè) ajax 過(guò)程
<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>
如果感覺(jué)這像是一段相當(dāng)普通的代碼,那就對(duì)了,正是如此!當(dāng)用戶(hù)在 city 或 state 字段中輸入新的值時(shí),callserver()方法就被觸發(fā),于是 ajax 開(kāi)始運(yùn)行了。有點(diǎn)兒明白怎么回事了吧?好,就是如此!
AJAX - X眾所周知,異步交互、JavaScript腳本和XML封裝數(shù)據(jù)是AJAX的三大特征。其實(shí),在實(shí)際應(yīng)用中,不需要牢牢套死這三條大律,在我看來(lái),AJAX - X,即去掉用XML封裝數(shù)據(jù),也不失為一種好的設(shè)計(jì)思路,如果應(yīng)用恰當(dāng),更顯輕盈步伐和巧妙思路。
一般讀取AJAX返回的XML結(jié)構(gòu)的數(shù)據(jù)時(shí)使用XMLHttp的responseXML對(duì)象屬性,同時(shí),XMLHttp也提供了另外一個(gè)屬性,即ResponseText,通過(guò)這個(gè)屬性,XMLHttp可以接受來(lái)自服務(wù)器的文本結(jié)構(gòu)的字符串信息。去掉XML的AJAX可以使用ResponseText這個(gè)對(duì)象屬性,很靈活的操控返回?cái)?shù)據(jù)的格式,可以自定義格式,比如我通常喜歡用c語(yǔ)言的那種文件流方式定義返回的字符串結(jié)構(gòu),有文件頭和具體的文件信息實(shí)體,文件頭分為狀態(tài)信息以及文件字符長(zhǎng)度,我摒棄了文件字符長(zhǎng)度的定義,規(guī)定死接受的ResponseTex字符串中的第一位為狀態(tài)碼,比如設(shè)定常量值0表示一起正常,非0的數(shù)字表示不正常,甚至有錯(cuò)誤等。如果有非0值,程序自動(dòng)取第二位起到257位(長(zhǎng)度為256)的字符串組成為狀態(tài)信息,從258位開(kāi)始到末尾的字符串就是服務(wù)器返回的正常結(jié)果信息。
substring(0,1)取狀態(tài)碼
substring(1,256)取服務(wù)器錯(cuò)誤信息(錯(cuò)誤信息不夠256位用空格補(bǔ)齊,取到數(shù)據(jù)后進(jìn)行Trim處理)
substring(256,末尾)取服務(wù)器返回的數(shù)據(jù)信息
三次substring即完成了一個(gè)簡(jiǎn)單但完整的交互工作。比起XML解析組件來(lái)說(shuō)要快的多。
用ResponseText比封裝為XML處理數(shù)據(jù)快和簡(jiǎn)單是一個(gè)原因,另一個(gè)原因是可操控性更大更靈活,打開(kāi)Google Suggest,在搜索框輸入字符可以給你給出拼寫(xiě)提示,Suggest就是應(yīng)用了AJAX技術(shù),不過(guò)它在從服務(wù)器返回?cái)?shù)據(jù)時(shí)并沒(méi)有使用XML封裝,也沒(méi)有自定義ResponseText格式,而是直接將返回代碼組織成js腳本,通過(guò)瀏覽器返回后直接執(zhí)行,如eval(XMLHttp.ResponseText)這樣的方式進(jìn)行執(zhí)行,http://www.google.com/complete/search?hl=en&js=true&qu=ajax 通過(guò)這個(gè)鏈接你可以看到Suggest利用AJAX得到的返回?cái)?shù)據(jù),此頁(yè)面是在Google Suggest的搜索框中輸入"AJAX"后得系統(tǒng)動(dòng)態(tài)返回的數(shù)據(jù)。
瀏覽器段拿到這段代碼后直接eval就可以了,至于sendRPCDone這個(gè)函數(shù),那當(dāng)然得實(shí)現(xiàn)定義后并裝載到頁(yè)面中啦。XMLHttp這個(gè)名字以XML開(kāi)頭,讓很多人禁錮了思想和創(chuàng)意,完全拋棄X,你也可以做出純AJAX的實(shí)例來(lái)。
當(dāng)然,對(duì)于大型系統(tǒng)來(lái)講,為了保持?jǐn)?shù)據(jù)接口的一致和整齊,還是用XML來(lái)傳遞更嚴(yán)謹(jǐn)更統(tǒng)一點(diǎn),聽(tīng)說(shuō)微軟已經(jīng)發(fā)起了重寫(xiě)XML Parse組件的號(hào)召,估計(jì)下一個(gè)版本的XMLHttp還是DOMParser還是MSXML2.DOMDocument都會(huì)大大提高效率,減少資源占用的。
Thinking in AJAX(二) —— 基于AJAX的WEB設(shè)計(jì)
一、AJAX最值得稱(chēng)贊的是異步交互,而不是無(wú)刷新
很多人都看好AJAX無(wú)刷新的技術(shù),以至于認(rèn)同AJAX就是用來(lái)做無(wú)刷新的。這個(gè)認(rèn)識(shí)是錯(cuò)誤的,什么是無(wú)刷新?無(wú)刷新就是頁(yè)面無(wú)需重載,那什么又是異步交互?異步交互就是一個(gè)簡(jiǎn)單的多線程,當(dāng)你在一個(gè)blog里看文章時(shí),同時(shí)也可以利用AJAX進(jìn)行無(wú)刷新的回復(fù)提交,看起來(lái)雖然也是無(wú)刷新,但這里最重要的是異步,即你能一邊看文章,一邊又能向服務(wù)器提交你的回復(fù)信息,利用好這個(gè)異步,才能算是掌握了AJAX的精髓。很多場(chǎng)合,無(wú)刷新是呈現(xiàn)給用戶(hù)的視覺(jué)體驗(yàn),而異步交互卻是默默無(wú)聞的工作在臺(tái)后,這種情況導(dǎo)致大多數(shù)人的錯(cuò)誤理解了AJAX的權(quán)重之分。
通過(guò)以上兩個(gè)國(guó)外成功應(yīng)用AJAX的網(wǎng)站,我們發(fā)現(xiàn)他們都使用的是輕量級(jí)的AJAX,就是那種交互簡(jiǎn)單,數(shù)據(jù)較少的操作。這也符合AJAX的本意,雖然像www.backbase.com和bindows都在RIA上有驚人的表現(xiàn)能力,但是速度慢、搜索引擎支持不好、開(kāi)發(fā)難度大等毛病還是無(wú)法讓用戶(hù)滿意的,請(qǐng)記住:AJAX的最終目的是為了提高用戶(hù)體驗(yàn),為了方便用戶(hù)交互,而不是因技術(shù)而技術(shù)的。
很多人認(rèn)為在成熟的框架中應(yīng)用AJAX會(huì)破壞框架的完整性,比較常見(jiàn)的說(shuō)法有三層架構(gòu)的WEB應(yīng)用中破壞MVC模式,其實(shí)不然。MVC的理論我就不多說(shuō)了,經(jīng)典的那三個(gè)層、五條線大家都很熟悉,在WEB應(yīng)用中,因?yàn)闉g覽器/服務(wù)器固有的這種請(qǐng)求/響應(yīng)的斷開(kāi)式網(wǎng)絡(luò)通訊模式,決定了在Model層無(wú)法實(shí)現(xiàn)主動(dòng)向View層發(fā)出數(shù)據(jù)更新事件,所以一般常見(jiàn)的成熟MVC框架中都將經(jīng)典MVC理論稍作修改:由Model層處理完業(yè)務(wù)后通知Control層,然后由Control層承擔(dān)向View發(fā)送數(shù)據(jù)更新的義務(wù)。但是AJAX天生具有監(jiān)聽(tīng)功能,AJAX實(shí)現(xiàn)異步響應(yīng)的那個(gè)OnReadyStateChange事件就具有在客戶(hù)端程序中才會(huì)有的事件監(jiān)聽(tīng)功能。現(xiàn)在想來(lái),利用AJAX實(shí)現(xiàn)的MVC模型有如下圖這樣: