XMLHttpRequest 和AJAX的愛恨情仇
AJAX 是 asynchronous javascript and XML 的簡寫,中文翻譯是異步的 javascript 和 XML,這一技術能夠向服務器請求額外的數據而無須卸載頁面,會帶來更好的用戶體驗。雖然名字中包含 XML ,但 AJAX 通信與數據格式無關
AJAX 包括以下幾步驟:1、創建 AJAX 對象;2、發出 HTTP 請求;3、接收服務器傳回的數據;4、更新網頁數據
概括起來,就是一句話,AJAX 通過原生的 XMLHttpRequest 對象發出 HTTP 請求,得到服務器返回的數據后,再進行處理
AJAX 技術的核心是 XMLHttpReques t對象(簡稱XHR),這是由微軟首先引入的一個特性,其他瀏覽器提供商后來都提供了相同的實現。XHR 為向服務器發送請求和解析服務器響應提供了流暢的接口,能夠以異步方式從服務器取得更多信息,意味著用戶單擊后,可以不必刷新頁面也能取得新數據
XMLHttpRequest 對象是當今所有 AJAX 和 Web 2.0 應用程序的技術基礎。盡管軟件經銷商和開源社團現在都在提供各種 AJAX 框架以進一步簡化 XMLHttpRequest 對象的使用;但是,我們仍然很有必要理解這個對象的詳細工作機制。
IE5 是第一款引入 XHR 對象的瀏覽器。在 IE5 中,XHR 對象是通過 MSXML 庫中的一個 ActiveX 對象實現的,而 IE7+ 及其他標準瀏覽器都支持原生的XHR對象
一、 引言
AJAX 利用一個構建到所有現代瀏覽器內部的對象 XMLHttpRequest 來實現發送和接收 HTTP 請求與響應信息。一個經由XMLHttpRequest 對象發送的 HTTP 請求并不要求頁面中擁有或回寄一個<form>元素。AJAX 中的"A"代表了"異步",這意味著XMLHttpRequest 對象的 send() 方法可以立即返回,從而讓 Web 頁面上的其它 HTML/JavaScript 繼續其瀏覽器端處理而由服務器處理HTTP 請求并發送響應。盡管缺省情況下請求是異步進行的,但是,你可以選擇發送同步請求,這將會暫停其它Web頁面的處理,直到該頁面接收到服務器的響應為止。
微軟在其 Internet Explorer(IE) 5 中作為一個 ActiveX 對象形式引入了 XMLHttpRequest 對象。其他的認識到這一對象重要性的瀏覽器制造商也都紛紛在他們的瀏覽器內實現了 XMLHttpRequest 對象,但是作為一個本地 JavaScript 對象而不是作為一個 ActiveX 對象實現。而如今,在認識到實現這一類型的價值及安全性特征之后,微軟已經在其 IE 7中把 XMLHttpRequest 實現為一個窗口對象屬性。幸運的是,盡管其實現(因而也影響到調用方式)細節不同,但是,所有的瀏覽器實現都具有類似的功能,并且實質上是相同方法。目前,W3C組織正在努力進行 XMLHttpRequest 對象的標準化,并且已經發行了有關該W3C規范的一個草案。
二、 XMLHttpRequest對象的屬性和事件
前端在接收到響應后,第一步是檢查status屬性,以確定響應已經成功返回。一般來說,可以將HTTP狀態碼為200作為成功的標志。此時,responseText屬性的內容已經就緒,而且在內容類型正確的情況下,responseXML也可以訪問了。此外,狀態碼為304表示請求的資源并沒有被修改,可以直接使用瀏覽器中緩存的版本;當然,也意味著響應是有效的
無論內容類型是什么,響應主體的內容都會保存到responseText屬性中,而對于非XML數據而言,responseXML屬性的值將為null
XMLHttpRequest對象暴露各種屬性、方法和事件以便于腳本處理和控制HTTP請求與響應。
-
readyState屬性
當XMLHttpRequest對象把一個HTTP請求發送到服務器時將經歷若干種狀態:一直等待直到請求被處理;然后,它才接收一個響應。這樣以來,腳本才正確響應各種狀態-XMLHttpRequest對象暴露一個描述對象的當前狀態的readyState屬性。
ReadyState取值 描述
0? 描述一種"未初始化"狀態;此時,已經創建一個XMLHttpRequest對象,但是還沒有初始化。
1? 描述一種"發送"狀態;此時,代碼已經調用了XMLHttpRequest open()方法并且XMLHttpRequest已經準備好把一個請求發送到服務器。
2? 描述一種"發送"狀態;此時,已經通過send()方法把一個請求發送到服務器端,但是還沒有收到一個響應。
3? 描述一種"正在接收"狀態;此時,已經接收到HTTP響應頭部信息,但是消息體部分還沒有完全接收結束。
4? 描述一種"已加載"狀態;此時,響應已經被完全接收。
-
onreadystatechange事件
無論readyState值何時發生改變,XMLHttpRequest對象都會激發一個readystatechange事件。其中,onreadystatechange屬性接收一個EventListener值-向該方法指示無論readyState值何時發生改變,該對象都將激活。
-
responseText屬性
字符串,只讀,請求不成功或者數據不完整,該屬性為null。如果返回的是JSON格式的數據,就可以調用responseText屬性
這個responseText屬性包含客戶端接收到的HTTP響應的文本內容。當readyState值為0、1或2時,responseText包含一個空字符串。當readyState值為3(正在接收)時,響應中包含客戶端還未完成的響應信息。當readyState為4(已加載)時,該responseText包含完整的響應信息。
-
responseXML屬性
此responseXML屬性用于當接收到完整的HTTP響應時(readyState為4)描述XML響應;此時,Content-Type頭部指定MIME(媒體)類型為text/xml,application/xml或以+xml結尾。如果Content-Type頭部并不包含這些媒體類型之一,那么responseXML的值為null。無論何時,只要readyState值不為4,那么該responseXML的值也為null。
其實,這個responseXML屬性值是一個文檔接口類型的對象,用來描述被分析的文檔。如果文檔不能被分析(例如,如果文檔不是良構的或不支持文檔相應的字符編碼),那么responseXML的值將為null。
-
status屬性
只讀屬性,HTTP狀態碼,是一個三位數的整數。其類型為short。而且,僅當readyState值為3(正在接收中)或4(已加載)時,這個status屬性才可用。當readyState的值小于3時試圖存取status的值將引發一個異常。
200,ok,響應成功。
301,Moved Permanently,永久移動。
302, Move temporarily,暫時移動
304, Not Modified,未修改
307, Temporary Redirect,暫時重定向
401, Unauthorized,未授權
403, Forbidden,禁止訪問
404, Not Found,未發現請求資源
500, Internal Server Error,服務器發生錯誤只有 2xx和304表示服務器正常。
-
statusText屬性
只讀屬性,一個字符串,表示服務器的狀態,是一個完整信息,如 “200 OK”。描述了HTTP狀態代碼文本;并且僅當readyState值為3或4才可用。當readyState為其它值時試圖存取statusText屬性將引發一個異常。
-
responseType屬性
類型可以是ArrayBuffer(內存緩沖區?不太理解)、Bold(前端的一個對象)、Documnet(Document對象即XML、html文檔或html片段)、JSON(JS對象)、text(字符串)。
xhr.responseType="json";
responseType 設置為 json,支持json的瀏覽器,主流瀏覽器都支持,會自動調用JSON.parese()方法,將JSON解析為JS對象,就是說 從xhr.response(不是xhr.responseText屬性)屬性得到的不是文本,是JS對象。
三、 XMLHttpRequest對象的方法
-
abort()方法
你可以使用這個abort()方法來暫停與一個XMLHttpRequest對象相聯系的HTTP請求,從而把該對象復位到未初始化狀態。
-
open()方法
調用open(DOMString method,DOMString uri,boolean async,DOMString username,DOMString password)方法初始化一個XMLHttpRequest對象。例如
xhr.open("get","example.php", false);
- 第一個參數用于指定發送請求的方式,這個字符串,不區分大小寫,但通常使用大寫字母。
"GET"用于常規請求,它適用于當URL完全指定請求資源,當請求對服務器沒有任何副作用以及當服務器的響應是可緩存的情況下"POST"方法常用于HTML表單。它在請求主體中包含額外數據且這些數據常存儲到服務器上的數據庫中。相同URL的重復POST請求從服務器得到的響應可能不同,同時不應該緩存使用這個方法的請求除了"GET"和"POST"之外,參數還可以是"HEAD"、"OPTIONS"、"PUT"。而由于安全風險的原因,"CONNECT"、"TRACE"、"TRACK"被禁止使用[注意]關于HTTP協議8種常用方法的詳細介紹移步至此
- 第二個參數是URL,該URL相對于執行代碼的當前頁面,且只能向同一個域中使用相同端口和協議的URL發送請求。如果URL與啟動請求的頁面有任何差別,都會引發安全錯誤。借助于window.document.baseURI屬性,該uri被解析為一個絕對的URI-換句話說,你可以使用相對的URI-它將使用與瀏覽器解析相對的URI一樣的方式被解析。
- 第三個參數async參數指定是否請求是異步的-缺省值為true。為了發送一個同步請求,需要把這個參數設置為false。
- 對于要求認證的服務器,你可以提供可選的用戶名和口令參數。在調用open()方法后,XMLHttpRequest對象把它的readyState屬性設置為1(打開)并且把responseText、responseXML、status和statusText屬性復位到它們的初始值。另外,它還復位請求頭部。注意,如果你調用open()方法并且此時readyState為4,則XMLHttpRequest對象將復位這些值。
在通過調用open()方法準備好一個請求之后,你需要把該請求發送到服務器。僅當readyState值為1時,你才可以調用send()方法;否則的話,XMLHttpRequest對象將引發一個異常。該請求被使用提供給open()方法的參數發送到服務器。當async參數為true時,send()方法立即返回,從而允許其它客戶端腳本處理繼續。在調用send()方法后,XMLHttpRequest對象把readyState的值設置為2(發送)。當服務器響應時,在接收消息體之前,如果存在任何消息體的話,XMLHttpRequest對象將把readyState設置為3(正在接收中)。當請求完成加載時,它把readyState設置為4(已加載)。對于一個HEAD類型的請求,它將在把readyState值設置為3后再立即把它設置為4。
-
send()方法
send()方法接收一個參數,即要作為請求主體發送的數據。調用send()方法后,請求被分派到服務器
如果是GET方法,send()方法無參數,或參數為null;
如果是POST方法,send()方法的參數為要發送的數據。對于發送數據為其它的數據類型,在調用send()方法之前,應該使用setRequestHeader()方法(見后面的解釋)先設置Content-Type頭部。如果在send(data)方法中的data參數的類型為DOMString,那么,數據將被編碼為UTF-8。如果數據是Document類型,那么將使用由data.xmlEncoding指定的編碼串行化該數據。
在POST方法中,向服務器提交的表單數據傳入send()中,序列化和轉碼處理,鍵值對用&連接,如 "name=jack&age=20",且在send()調用前,需要設置MIME類型:xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
-
setRequestHeader()方法
該setRequestHeader(DOMString header,DOMString value)方法用來設置請求的頭部信息。當readyState值為1時,你可以在調用open()方法后調用這個方法;否則,你將得到一個異常。
-
getResponseHeader()方法
getResponseHeader(DOMString header,value)方法用于檢索響應的頭部值。僅當readyState值是3或4(換句話說,在響應頭部可用以后)時,才可以調用這個方法;否則,該方法返回一個空字符串。
-
getAllResponseHeaders()方法
該getAllResponseHeaders()方法以一個字符串形式返回所有的響應頭部(每一個頭部占單獨的一行)。如果readyState的值不是3或4,則該方法返回null。
四、 發送請求
在AJAX中,許多使用XMLHttpRequest的請求都是從一個HTML事件(例如一個調用JavaScript函數的按鈕點擊(onclick)或一個按鍵(onkeypress))中被初始化的。AJAX支持包括表單校驗在內的各種應用程序。有時,在填充表單的其它內容之前要求校驗一個唯一的表單域。例如要求使用一個唯一的UserID來注冊表單。如果不是使用AJAX技術來校驗這個UserID域,那么整個表單都必須被填充和提交。如果該UserID不是有效的,這個表單必須被重新提交。例如,一個相應于一個要求必須在服務器端進行校驗的Catalog ID的表單域可能按下列形式指定:? ?
<form name="validationForm" action="validateForm" method="post">?
<table>?<tr><td>Catalog Id:</td>?<td>?<input type="text" size="20" id="catalogId" name="catalogId" autocomplete="off" onkeyup="sendRequest()">?</td>?<td><div id="validationMessage"></div></td>?</tr>?
</table></form>
前面的HTML使用validationMessage div來顯示相應于這個輸入域Catalog Id的一個校驗消息。onkeyup事件調用一個JavaScript sendRequest()函數。這個sendRequest()函數創建一個XMLHttpRequest對象。創建一個XMLHttpRequest對象的過程因瀏覽器實現的不同而有所區別。如果瀏覽器支持XMLHttpRequest對象作為一個窗口屬性(所有普通的瀏覽器都是這樣的,除了IE 5和IE 6之外),那么,代碼可以調用XMLHttpRequest的構造器。如果瀏覽器把XMLHttpRequest對象實現為一個ActiveXObject對象(就象在IE 5和IE 6中一樣),那么,代碼可以使用ActiveXObject的構造器。下面的函數將調用一個init()函數,它負責檢查并決定要使用的適當的創建方法-在創建和返回對象之前。
<script type="text/javascript">?
function sendRequest(){?var xmlHttpReq=init();?function init(){?if (window.XMLHttpRequest) {?return new XMLHttpRequest();?}else if (window.ActiveXObject) {?return new ActiveXObject("Microsoft.XMLHTTP");?}}
}
</script>??
接下來,你需要使用Open()方法初始化XMLHttpRequest對象-指定HTTP方法和要使用的服務器URL。
var catalogId=encodeURIComponent(document.getElementById("catalogId").value);
xmlHttpReq.open("GET", "validateForm?catalogId=" + catalogId, true);
默認情況下,使用XMLHttpRequest發送的HTTP請求是異步進行的,但是你可以顯式地把async參數設置為true,如上面所展示的。
在這種情況下,對URL validateForm的調用將激活服務器端的一個servlet,但是你應該能夠注意到服務器端技術不是根本性的;實際上,該URL可能是一個ASP,ASP.NET或PHP頁面或一個Web服務-這無關緊要,只要該頁面能夠返回一個響應-指示CatalogID值是否是有效的-即可。因為你在作一個異步調用,所以你需要注冊一個XMLHttpRequest對象將調用的回調事件處理器-當它的readyState值改變時調用。記住,readyState值的改變將會激發一個readystatechange事件。你可以使用onreadystatechange屬性來注冊該回調事件處理器。
xmlHttpReq.onreadystatechange=processRequest;
然后,我們需要使用send()方法發送該請求。因為這個請求使用的是HTTP GET方法,所以,你可以在不指定參數或使用null參數的情況下調用send()方法。
xmlHttpReq.send(null);
五、 處理請求
在這個示例中,因為HTTP方法是GET,所以在服務器端的接收servlet將調用一個doGet()方法,該方法將檢索在URL中指定的catalogId參數值,并且從一個數據庫中檢查它的有效性。
本文示例中的這個servlet需要構造一個發送到客戶端的響應;而且,這個示例返回的是XML類型,因此,它把響應的HTTP內容類型設置為text/xml并且把Cache-Control頭部設置為no-cache。設置Cache-Control頭部可以阻止瀏覽器簡單地從緩存中重載頁面。
// java 代碼,我不是很懂。。
public void doGet(HttpServletRequest request,??
HttpServletResponse response)?
throws ServletException, IOException {?...?...?response.setContentType("text/xml");?response.setHeader("Cache-Control", "no-cache");?
}??
來自于服務器端的響應是一個XML DOM對象,此對象將創建一個XML字符串-其中包含要在客戶端進行處理的指令。另外,該XML字符串必須有一個根元素。
out.println("<catalogId>valid</catalogId>");
【注意】XMLHttpRequest對象的設計目的是為了處理由普通文本或XML組成的響應;但是,一個響應也可能是另外一種類型,如果用戶代理(UA)支持這種內容類型的話。
當請求狀態改變時,XMLHttpRequest對象調用使用onreadystatechange注冊的事件處理器。因此,在處理該響應之前,你的事件處理器應該首先檢查readyState的值和HTTP狀態。當請求完成加載(readyState值為4)并且響應已經完成(HTTP狀態為"OK")時,你就可以調用一個JavaScript函數來處理該響應內容。下列腳本負責在響應完成時檢查相應的值并調用一個processResponse()方法。
function processRequest(){if(xmlHttpReq.readyState==4){if(xmlHttpReq.status==200){processResponse();}}
}
該processResponse()方法使用XMLHttpRequest對象的responseXML和responseText屬性來檢索HTTP響應。如上面所解釋的,僅當在響應的媒體類型是text/xml,application/xml或以+xml結尾時,這個responseXML才可用。這個responseText屬性將以普通文本形式返回響應。對于一個XML響應,你將按如下方式檢索內容:
var msg=xmlHttpReq.responseXML;
借助于存儲在msg變量中的XML,你可以使用DOM方法getElementsByTagName()來檢索該元素的值:
var catalogId=msg.getElementsByTagName("catalogId")[0].firstChild.nodeValue;
最后,通過更新Web頁面的validationMessage div中的HTML內容并借助于innerHTML屬性,你可以測試該元素值以創建一個要顯示的消息:
if(catalogId=="valid"){var validationMessage = document.getElementById("validationMessage");validationMessage.innerHTML = "Catalog Id is Valid";
}
else
{var validationMessage = document.getElementById("validationMessage");validationMessage.innerHTML = "Catalog Id is not Valid";
}
六、兼容性問題
window.ActiveXObject 對象
創建XMLHttpRequest 對象,必須考慮到瀏覽器兼容問題,有時可以利用判斷瀏覽器是否支持ActiveX控件,如果瀏覽器支持ActiveX控件可以:
- var xml=new ActiveXObject("Microsoft.XMLHTTP");創建XMLHttpRequest 對象(IE7以前的版本中);
- var xml=new ActiveXObject("Msxml2.XMLHTTP");在較新的IE版本中創建XMLHttpRequest對象;
- var xml=new XMLHttpRequest();而在IE7以后及非IE瀏覽器中創建XMLHttpRequest對象。
首先我們來看看怎么來聲明(使用)它,在使用XMLHTTPRequest對象發送請求和處理響應之前,我們必須要用javascript創建一個XMLHTTPRequest對象。(IE把XMLHTTPRequest實現為一個ActiveX對象,其他的瀏覽器[如Firefox/Safari/Opear]則把它實現為一個本地的javascript對象)。下面我們就來看看具體怎么運用javascript來創建它吧:
<script language="javascript" type="text/javascript">
var xmlhttp;
// 創建XMLHTTPRequest對象
function createXMLHTTPRequest(){if(window.ActiveXObject){ // 判斷是否支持ActiveX控件xmlhttp = new ActiveObject("Microsoft.XMLHTTP"); // 通過實例化ActiveXObject的一個新實例來創建XMLHTTPRequest對象}else if(window.XMLHTTPRequest){ // 判斷是否把XMLHTTPRequest實現為一個本地javascript對象xmlhttp = new XMLHTTPRequest(); // 創建XMLHTTPRequest的一個實例(本地javascript對象)}
}
</script>
js用來區別IE與其他瀏覽器及IE6-8之間的方法。
- document.all
- !!window.ActiveXObject;
使用方法如下:
if (document.all){alert(”IE瀏覽器”);
}else{alert(”非IE瀏覽器”);
}
if (!!window.ActiveXObject){alert(”IE瀏覽器”);
}else{alert(”非IE瀏覽器”);
}
區別IE6、IE7、IE8之間的方法:
var isIE=!!window.ActiveXObject;
var isIE6=isIE&&!window.XMLHttpRequest;
var isIE8=isIE&&!!document.documentMode;
var isIE7=isIE&&!isIE6&&!isIE8;
if (isIE){if (isIE6){alert(”ie6″);}else if (isIE8){alert(”ie8″);}else if (isIE7){alert(”ie7″);}
}
據說火狐以后也會加入document.all這個方法,所以建議使用第二種方法,應該會安全一些。
七、 小結
上面就是XMLHttpRequest對象使用的所有細節實現。通過不必把Web頁面寄送到服務器而實現數據傳送,XMLHttpRequest對象為客戶端與服務器之間提供了一種動態的交互能力。你可以使用JavaScript啟動一個請求并處理相應的返回值,然后使用瀏覽器的DOM方法更新頁面中的數據。
附:簡單的示例代碼
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">?
<html xmlns="http://www.w3.org/1999/xhtml">?
<head runat="server">?<meta http-equiv="Content-Type" content="text/html;charset=gb2312" />?<title>示例</title>?<script src="Ajax.js"></script>?
</head>?
<body>????
<a href="#" onclick="ajaxReader('data5.xml'); return false"> 查看</a>?
<div id="dataArea">?
</div>?????????????
</body>?
</html>?
script:?
// JScript File?
function? ajaxReader(file)?{//定義xmlobj對象?var xmlObj=null;?if(window.XMLHttpRequest)? // 用 XMLHttpRequest 方式{xmlObj=new XMLHttpRequest();?}else if(window.ActiveXObject)? // 用 ActiveXObject 方式{xmlObj=new ActiveXObject("Microsoft.XMLHTTP");?}else?{return;?}// 第一種方式xmlObj.onreadystatechange=function(){ // 值發生變化時觸發if(xmlObj.readyState==4)?{ // 判斷processXML(xmlObj.responseXML);??}}?xmlObj.open('GET',file,true);? // 發送請求xmlObj.send('');? // send 發送 <!--// 第二種方式,下載docx// IE 版本xmlObj.open('GET',url,true);xmlObj.responseType = 'blob'xmlObj.setRequestHeader("Authorization", 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')//post請求一定要添加請求頭才行不然會報錯if(model.type=='post'){xhr.setRequestHeader("Content-type","application/json");}xmlObj.onload = function(){if (this.status==200){var ret_data = this.response // this.response也就是請求的返回就是Blob對象var blob = new Blob([res.data.fileData],{type: 'application/json;charset=utf-8'}); //application/vnd.openxmlformats-officedocument.wordprocessingml.document這里表示doc類型womdow.navigator.msSaveBlob(blob,data['file_name'])}xmlObj.send('');? }// 谷歌瀏覽器 創建a標簽 添加download屬性下載var downloadElement = document.createElement('a');downloadElement.href = href;downloadElement.target = '_blank';downloadElement.download = 'model.json'; //下載后文件名document.body.appendChild(downloadElement);downloadElement.click(); //點擊下載document.body.removeChild(downloadElement); //下載完成移除元素window.URL.revokeObjectURL(href); //釋放掉blob對象-->}?
function processXML(obj)?
{var dataArray=obj.getElementsByTagName('pets')[0].childNodes;var dataArrayLength=dataArray.length;var insertData='<table>';insertData +='<tr><th>Pet5</th><th>Tasks</th></tr>';?for(var i=0;i<dataArrayLength;i++)?{?if(dataArray[i].tagName)?{?insertData+='<tr>';?????insertData +='<td>'+dataArray[i].tagName+'</td>';?insertData +="<td>"+dataArray[i].getAttribute('tasks')+'</td>';?insertData+='</tr>';????}?}insertData +=+'</table>';?document.getElementById('dataArea4').innerHTML =insertData;?
}?