Ajax(Asynchronous JavaScript + XML的簡寫)可以向服務器請求數據而無需卸載(刷新)頁面,帶來更好的用戶體驗。
Ajax技術的核心是XMLHttpRequest對象(簡稱XHR)。
一、XMLHttpRequest對象
- /* 兼容IE早期版本 */
- function createXHR(){
- if (typeof XMLHttpRequest != "undefined"){
- return new XMLHttpRequest();
- } else if (typeof ActiveXObject != "undefined"){ // 適用於IE7之前的版本
- if (typeof arguments.callee.activeXString != "string"){
- var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
- "MSXML2.XMLHttp"],
- i, len;
- for (i=0,len=versions.length; i < len; i++){
- try {
- new ActiveXObject(versions[i]);
- arguments.callee.activeXString = versions[i];
- break;
- } catch (ex){
- //skip
- }
- }
- }
- return new ActiveXObject(arguments.callee.activeXString);
- } else { // XHR對象和ActiveX對象都不存在,則拋出錯誤
- throw new Error("No XHR object available.");
- }
- }
1. XHR的用法
xhr.open("請求的類型get|post等", "請求的URL", "是否異步發送請求");
說明:
(1)URL相對於執行代碼的當前頁面(當然也可以使用絕對路徑)
(2)open()方法並不會真正發送請求,而只是啓動一個請求以備發送
xhr.send("請求主體發送的數據");
說明:
(1)如果不需要通過請求主體發送數據(比如get請求),則必須傳入null,因爲這個參數對有些瀏覽器來說是必需的
(2)調用send()之後,請求就會被分派到服務器
補充:xhr.open()方法爲“false”,即同步請求,JavaScript代碼會等到服務器響應後再繼續執行;否則,繼續執行後續代碼。
在收到服務器響應後,相應的數據會自動填充XHR對象的屬性。
- responseText:作爲響應主體被返回的文本
- responseXML:如果響應的內容類型是”text/xml”或”application/xml”,這個屬性中將保存包含着響應數據的XML DOM文檔
- status:響應的HTTP狀態
- statusText:HTTP狀態的說明
- // 爲確保接收到適當的響應 200:成功;304:資源未被修改
- if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
- console.log(xhr.responseText);
- }
說明:
(1)有的瀏覽器會錯誤的報告成功狀態碼爲204
(2)無論內容類型是什麼,響應主體的內容都會保存到responseText屬性中;而對於XML數據而言,responseXML同時也將被賦值,否則其值爲null
對於異步請求,可以檢測XHR對象的readyState屬性,該屬性表示請求/響應過程的當前活動階段
- 0:未初始化。尚未調用open()方法
- 1:啓動。已經調用open()方法,但尚未調用send()方法
- 2:發送。已經調用send()方法,但尚未接收到響應
- 3:接收。已經接收到部分響應數據
- 4:完成。已經接收全部響應數據,而且已經可以在客戶端使用了。
readyState屬性的值發生變化,都會觸發readystatechange事件。可以利用這個事件來檢測每次狀態變化後readyState的值。不過,必須在調用open()之前指定onreadystatechange事件處理程序才能確保跨瀏覽器兼容性。
- var xhr = createXHR();
- xhr.onreadystatechange = function(event){
- // 不要使用this,作用域會產生問題,在部分瀏覽器中會執行失敗
- if (xhr.readyState == 4){
- if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
- console.log(xhr.responseText);
- } else {
- console.log("Request was unsuccessful: " + xhr.status);
- }
- }
- };
- xhr.open("get", "example.txt", true);
- xhr.send(null);
在接收到響應數據之前可以調用abort()方法來取消異步請求:
- xhr.abort();
- xhr = null; // 解除引用,釋放內存
2. HTTP頭部信息
setRequestHeader():設置自定義的請求頭信息。必須在調用open()方法之後且調用send()方法之前調用。
getResponseHeader() getAllResponseHeaders():可以獲取指定(全部)響應頭信息。
- var xhr = createXHR();
- xhr.onreadystatechange = function(){};
- xhr.open("get", "example.php", true);
- xhr.setRequestHeader("MyHeader", "MyValue");
- xhr.send(null);
3. GET請求
open()方法的URL尾部的查詢字符串必須經過正確的編碼
- function addURLParam(url, name, value) {
- url += (url.indexOf("?") == -1 ? "?" : "&");
- url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
- return url;
- }
- var url = "http://test.com";
- url = addURLParam(url, "uid" , 5);
- url = addURLParam(url, "siteid", 123); // "http://test.com?uid=5&siteid=123"
- xhr.open("get", url, true);
- xhr.send(null);
4. POST請求
POST請求將數據作爲請求的主體
- /* 序列化表單 */
- function serialize(form){
- var parts = new Array();
- var field = null;
- for (var i=0, len=form.elements.length; i < len; i++){
- field = form.elements[i];
- switch(field.type){
- case "select-one":
- case "select-multiple":
- for (var j=0, optLen = field.options.length; j < optLen; j++){
- var option = field.options[j];
- if (option.selected){
- var optValue = "";
- if (option.hasAttribute){
- optValue = (option.hasAttribute("value") ?
- option.value : option.text);
- } else {
- optValue = (option.attributes["value"].specified ?
- option.value : option.text);
- }
- parts.push(encodeURIComponent(field.name) + "=" +
- encodeURIComponent(optValue));
- }
- }
- break;
- case undefined: //fieldset
- case "file": //file input
- case "submit": //submit button
- case "reset": //reset button
- case "button": //custom button
- break;
- case "radio": //radio button
- case "checkbox": //checkbox
- if (!field.checked){
- break;
- }
- /* falls through */
- default:
- parts.push(encodeURIComponent(field.name) + "=" +
- encodeURIComponent(field.value));
- }
- }
- return parts.join("&");
- }
- /* 發送請求 */
- function submitData(){
- var xhr = createXHR();
- xhr.onreadystatechange = function(event){
- if (xhr.readyState == 4){
- if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
- alert(xhr.responseText);
- } else {
- alert("Request was unsuccessful: " + xhr.status);
- }
- }
- };
- xhr.open("post", "postexample.php", true);
- // 表單提交的內容類型
- xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
- var form = document.getElementById("user-info");
- // 請求主體爲數據
- xhr.send(serialize(form));
- }
二、XMLHttpRequest 2級
XMLHttpRequest 1級只是把已有的XHR對象的實現細節描述了出來。而XMLHttpRequest 2級則進一步發展了XHR。並非所有瀏覽器都完整地實現了XMLHttpRequest 2級規範,但所有瀏覽器都實現了它規定的部分內容。
1. FormData
- // 創建FormData對象
- var data = new FormData();
- data.append("name", "ligang");
- // 用表單元素填充
- xhr.open("post", "postexample.php", true);
- var form = document.getElementById("user-info");
- // 使用FormData的方便之處在於不必明確地在XHR對象上設置請求頭。
- // xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
- xhr.send(new FormData(form));
2. 超時設定
IE8爲XHR對象添加了一個timeout屬性,表示請求在等待響應多少毫秒後就終止。
- xhr.open("get", "timeout.php", true);
- xhr.timeout = 60 * 1000;
- xhr.ontimeout = function(){
- alert("Request did not return in a second.");
- };
- xhr.send(null);
對於其他瀏覽器的兼容做法
- xhr.open("get", "timeout.php", true);
- xhr.onreadystatechange = function(event){
- if (xhr.readyState == 4){
- // 清除定時器
- clearTimeout(timeout);
- if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
- console.log(xhr.responseText);
- } else {
- console.log("Request was unsuccessful: " + xhr.status);
- }
- }
- };
- // 設置超時時間 1分鐘
- var timeout = setTimeout(function() {
- xmlHttpRequest.abort();
- xmlHttpRequest = null;
- }, 60 * 1000);
- xmlHttpRequest.send(null);
3. overrideMimeType()方法
重寫XHR響應的MIME類型,必須在send()方法之前。
如果,服務器返回的MIME類型是text/plain,但數據中實際包含的是XML。根據MIME類型,responseXML屬性中仍然是null。此時,通過overrideMimeType()方法,可以保證把響應當作XML而非純文本來處理(即,responseXML中被賦值)。
- var xhr = createXHR();
- xhr.open("get", "text.php", true);
- xhr.overrideMimeType("text/xml");
- xhr.send(null);
三、進度事件
6個進度事件:
- loadstart:在接收到響應數據的第一個字節時觸發。
- progress:在接收響應期間持續不斷地觸發。
- error:在請求發生錯誤時觸發。
- abort:在因爲調用abort()方法而終止時觸發。
- load:在接收到完整的響應數據時觸發。
- loadend:在通信完成或者觸發error、abort或load事件後觸發。
1. load事件
可以代替readystatechagne事件。其處理程序會接收到一個event對象,其target屬性指向XHR對象實例,因而可以訪問到XHR對象的所有方法和屬性。然而,並非所有瀏覽器都實現了事件對象。
2. progress事件
其處理程序會接收一個event對象,其target屬性指向XHR對象實例,但包含着三個額外的屬性
- lengthComputable:是一個表示進度信息是否可用的布爾值
- position:表示已經接收的字節數
- totalSize:根據content-length響應頭確定的預期字節數
注意:其必須在調用open()方法之前添加
- var xhr = createXHR();
- xhr.onload = function(event){
- // event.target存在兼容性問題,所以只能使用xhr
- if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
- console.log(xhr.responseText);
- } else {
- console.log("Request was unsuccessful: " + xhr.status);
- }
- };
- xhr.onprogress = function(event){
- var divStatus = document.getElementById("status");
- if (event.lengthComputable){
- divStatus.innerHTML = "Received " + event.position + " of " + event.totalSize + " bytes";
- }
- };
- xhr.open("get", "altevents.php", true);
- xhr.send(null);
四、跨源資源共享
CORS(Cross-Origin Resource Sharing)背後的基本思想,就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功還是失敗。
在發送請求時,給其附加一個額外的Origin頭部,其中包含請求頁面的源信息(協議、域名和端口),以便服務器根據這個頭部信息來決定是否給予響應。
Origin: http://www.test.com
如果服務認爲這個請求可以接受,在Access-Control-Allow-Origin頭部中回發相同的源信息(如果是公共資源,可以回發”*”)。
Access-Control-Allow-Origin: http://www.test.com
注意:請求和響應都不包含cookie信息。
1. IE中實現CORS:XDR(XDomainRequest),所有的XDR請求都是異步的,不能創建同步請求。其使用方法類似於XHR。
2. 其他瀏覽器對CORS的實現:通過XMLHttpRequest對象實現對CORS的原生支持。只需給open()方法傳入絕對地址。支持同步請求。
跨域XHR對象的安全限制:
(1)不能使用setRequestHeader()設置自定義頭部。
(2)不能發送和接收cookie。
(3)調用getAllResponseHeaders()方法總會返回空字符串。
建議:訪問本地資源,最好使用相對URL;訪問遠程資源,使用絕對URL。
3. 跨瀏覽器的CORS
- function createCORSRequest(method, url){
- var xhr = new XMLHttpRequest();
- if ("withCredentials" in xhr){ // 檢測XHR是否支持CORS的簡單方式,就是檢測是否存在withCredentials屬性
- xhr.open(method, url, true);
- } else if (typeof XDomainRequest != "undefined"){ // IE XDR
- xhr = new XDomainRequest();
- xhr.open(method, url);
- } else {
- xhr = null;
- }
- return xhr;
- }
- var request = createCORSRequest("get", "http://www.somewhere-else.com/xdr.php");
- if (request){
- request.onload = function(){
- //do something with request.responseText
- };
- request.send();
- }
五、其他跨域技術
利用DOM中能夠執行跨域請求的功能,在不依賴XHR對象的情況下也能發送某種請求,其不需要修改服務器端代碼。
1. 圖像Ping
<img>
標籤,可以從任何網頁中加載圖像,無需關注是否跨域。這也是廣告跟蹤瀏覽量的主要方式。
圖像Ping是與服務器進行簡單、單向的跨域通信的一種方式。瀏覽器得不到任何具體的數據。但通過監聽load和error事件,可以知道響應是什麼時間接收到的。
- var img = new Image();
- img.onload = img.error = function() {
- console.log("Done!");
- };
- img.src = "http://www.test.com/getImage?id=1";
缺點:
(1)只能發送Get請求
(2)無法訪問服務器的響應文本
2. JSONP(JSON with padding)
兩部分組成:回調函數和數據。
回調函數是當響應到來時應該在頁面調用的函數。回到函數的名字一般是在請求中指定的。而數據是傳入回調函數中的JSON數據。
JSONP是通過動態<script>
元素來使用的
- function handleResponse(response){
- alert("You're at IP address " + response.ip + ", which is in " + response.city + ", " + response.region_name);
- }
- var script = document.createElement("script");
- script.src = "http://freegeoip.net/json/?callback=handleResponse";
- document.body.insertBefore(script, document.body.firstChild);
優點:能夠直接訪問響應文本,支持在瀏覽器與服務器之間雙向通信。
缺點:
(1)JSONP是從其他域中加載代碼執行,其安全性無法確保。
(2)不能很容易的確定JSONP請求是否失敗。
3. Comet
更高級的Ajax技術,服務器向頁面推送數據。
兩種實現Comet的方式:長輪詢和流。
(1)長輪詢:頁面發起一個到服務器的請求,然後服務器一直保持連接打開,直到有數據可發送。發送完數據之後,瀏覽器關閉連接,隨即又發起一個到服務器的新請求。【區別:短輪詢,服務器立即發送響應,無論是否有效,而長輪詢是等待發送響應。】
(2)HTTP流:生命週期內只使用一個HTTP連接。瀏覽器向服務器發送一個請求,而服務器保持連接打開,然後週期性地向瀏覽器發送數據。
- /**
- * progress:接收數據時調用的函數
- * finished:關閉連接時調用的函數
- */
- function createStreamingClient(url, progress, finished){
- var xhr = new XMLHttpRequest(),
- received = 0;
- xhr.open("get", url, true);
- xhr.onreadystatechange = function(){
- var result;
- if (xhr.readyState == 3){
- //get only the new data and adjust counter
- result = xhr.responseText.substring(received);
- received += result.length;
- //call the progress callback
- progress(result);
- } else if (xhr.readyState == 4){
- finished(xhr.responseText);
- }
- };
- xhr.send(null);
- return xhr;
- }
- var client = createStreamingClient("streaming.php",function(data){alert("Received: " + data);}, function(data){alert("Done!");});
服務器發送事件:SSE和事件流
4. Web Sockets
目標是在一個單獨的持久連接上提供全雙工、雙向通信。
優點:能夠在客戶端和服務器之間發送非常少量的數據,而不必擔心HTTP那樣字節級的開銷。
缺點:制定協議的時間比制定JavaScript API的時間還要長。
- // 必須給WebSocket構造函數傳入絕對URL
- var socket = new WebSocket("ws://www.example.com/server.php");
- // 向服務器發送數據(只能發送純文本,其他數據需要序列化)
- socket.send("Hello");
- // 接收服務器的響應數據
- socket.onmessage = function(event) {
- var data = event.data;
- };
其他事件:
- open:在成功建立連接時觸發。
- error:在發生錯誤時觸發,連接不能持續。
- close:在連接關閉時觸發。
注意:WebSocket對象不支持DOM 2級事件偵聽器,必須使用DOM 0級語法分別定義各個事件。