博客 / 詳情

返回

Ajax全面解析(來自JavaScript高級程序設計)

Ajax的全稱就是Asynchronous JavaScript + XML,它的本意是為了可以在不刷新頁面的情況下異步更新數據。

而實現這一功能的核心就是XMLHttpRequest(XHR),它能夠獲取新數據,並通過DOM操作將新數據插入到頁面當中。

雖然它的名稱中包含有XML,但其實Ajax通信與數據格式沒有關係,Ajax可以返回HTML、XML、json、jsonp、text、script等數據類型。

Ajax的一個基本流程

在IE7版本之後,以及Firefox、Opera、Chrome、Safari瀏覽器中,我們可以直接使用XMLHttpRequest構造函數來創建最新的XML對象。

var xhr = new XMLHttpRequest();

在使用XML對象的第一步是使用open方法,open接受3個參數:

要發送請求的類型("get"、"post"等)、請求的地址以及是否異步發送請求的布爾值。

xhr.open("get","example.php",false)

其中的第二個參數必須是在同一個域中使用相同端口和協議的地址,否則會引發安全錯誤。

在這之後調用xhr.send()方法,如果是POST請求的話,send裏就需要放入要傳的信息

在收到服務器的響應之後,JavaScript會自動填充XHR對象的屬性,常用到的屬性有以下幾個:

  • responseText:響應的文本
  • status:響應的HTTP狀態
  • statusText:HTTP狀態的説明

首先應該做的是檢查status屬性,如果是200的話,那麼就説明本次響應已經成功了,或者是304的話,説明本地瀏覽器有緩存,可以直接調用。

同時我們一般是使用異步發送請求,所以我們還得檢測XHR對象的readyState屬性,該屬性表示請求/響應過程的當前階段。

  • 0:尚未調用open()方法
  • 1:啓動open()方法,但還沒啓動send()
  • 2:調用send(),但還未收到響應
  • 3:已經接收到部分信息
  • 4:已經收到全部信息

每一個階段的改變都會觸發一次onreadystatechange事件。

所以一個基本的Ajax流程的例子如下所示:

var xhr = new XMLHttpRequest();
xhr.open("get","example.php",false);
xhr.send();
xhr.onreadystatechange = function (){
   if (xhr.readyState == 4){
     if ((xhr.status >= 200 && xhr.status < 300)) || xhr.status == 304){
         alert(xhr.responseText)
     }else {
         alert(xhr.status)
     }
   }
}
 

XMLHttpRequest2級

XHR1級已經把Ajax的實現細節都給描述出來了,在此基礎上,XHR2級對整個XHR進行了升級進化。

主要有以下幾個方面:

  • FormData:

用於序列化表單以及創建與表單格式相同的數據,使用new創建並且直接通過send函數發送。

創建時可以直接傳入鍵值對

var data = new FormData();
data.append("name", "Tuncan")

或者預先傳入表單元素

var data = new FromData(document.forms[0])
  • 超時設定

XHR對象新增了一個timeout屬性,表示請求在多少時間之後便會終止,在給定時間到達之後,還沒有接到響應便會觸發timeout事件並調用omtimeout事件處理程序。

xhr.timeout = 1000;
xhr.ontimeout = function (){
    alert("Request did not return in a second")
}
  • overrideMimeType

用於重寫XHR的MIME類型,該類型決定XHR對象如何處理返回的響應,例如如果服務器返回的MIME類型是text/plain,但裏面的文檔類型是XML,根據MIME類型XHR的responseXML是null,所以需要overrideMimeType在send調用前重寫MIME類型為XML才能處理。

var xhr = new XMLHttpRequest();
xhr.open("get","text.php",true);
xhr.overrideMimeType("text/xml");
xhr.send(null);

XMLHttpRequest的進度事件

在剛剛使用XHR實現Ajax的流程當中,可以發現整個響應會分為不同的階段,在W3C裏為其定義為了6個進度事件:

loadstart:接收到響應數據的第一個字節時觸發
progress:接受響應期間不斷地觸發
error:請求錯誤時觸發
abort:調用abort()方法終止連接時觸發
load:接收到完整響應時觸發
loadend:通信完成或者觸發error、abort或load事件時觸發

每一個請求都是以loadstart開始,經歷一個或多個progress,然後接到error、abort或是load中的一個,最後以loadened結束。

其中的load事件和progress事件有一些細節需要注意:

  • load事件

Firefox曾致力於簡化異步交互模型,所以引入load事件來代替readystatechange事件,響應接受完畢之後才會觸發load事件,因此也沒有必要去檢查readyState屬性了。

xhr.onload = function(){
    if ((xhr.status >= 200 && xhr.status < 300)) || xhr.status == 304){
       alert(xhr.responseText)
    }else {
       alert(xhr.status)
    }
}
  • progress事件

progress事件用以表示當前接收數據的進度,它會被週期性地觸發,每次觸發都會返回一個event對象,event對象的target屬性是XHR對象

同時它還含有三個屬性:

lengthComputable:進度信息是否可用
position:表示已接收的字節數
totalSize:表示響應頭部的預期字節數

xhr.onprogress = function (event){
    var status = document.getElementById("status")
    if (event.lengthComputable){
        status.innerHTML = "Received" + event.positon + "of" + event.totalSize
    }
}

需要注意的是要在xhr.open()之前定義好該事件

跨域資源共享——突破XHR的限制

因為在默認情況下,頁面通過XHR只能訪問跟它處在同一個域中的資源,這是來源於跨域安全策略。

但是在實際額開發過車個當中,我們一定會遇上需要進行跨域請求的地方,那麼這時候就需要用上CORS了

CORS(Cross-Orign Resource Sharing,跨域資源共享),其基本思想就是使用自定義的Http頭部實現瀏覽器與服務器之間的溝通,從而確定請求是否成功。

例如,對於一個請求,在頭部里加入Origin

Origin: http://www.nczonline.net

如果服務器認為該請求可以接受,就在Access-Control-Allow-Origin頭部中回發相同的源信息

Access-Control-Allow-Origin: http://www.nczonline.net

這樣就完成了一次CORS。

跨域資源共享在非IE瀏覽器中,如Firefox3.5+,Safari 4+,Chrome,iOS版Safari和Android的WebKit中,都通過XMLHttpRequest實現了對CORS的原生支持。處於安全考慮,加上了如下的限制:

  • 不能使用自定義頭部
  • 不能發送和接受Cookie
  • getAllResponseHeaders()總會返回空字符串

在IE瀏覽器當中,主要是通過引入XDR對象,創建時通過new XDomainRequest穿件即可

該對象與XHR最重要的不同之處就在於不能使用Cookie,只支持GET和POST請求,以及只支持異步請求

如果開發一定要使用Cookie的話,可以將withCredentials屬性設置為true,如果服務器接受該請求,就會使用Access-Control-Allow-Credentials: true來響應

最後如果想要實現跨瀏覽器的CORS時,通過檢查XHR中是否帶有withCredentials屬性,再結合檢查XDomainRequest是否存在,就可以兼顧所有瀏覽器了。

function createCORSRequest (method,url){
    var xhr = new XMLHttpRequest();
    if ("withCredentials" in xhr){
       xhr.open(method,url,true);
    }else if ( typeof XDomainRequest != "undefined"){
       xhr = new XDomainRequest();
       xhr.open(method,url) 
    }else {
       xhr = null
    }
    
    return xhr    
}

其它的跨域方式

雖然説現在CORS已經無處不在,但在其發明出來之前,要想實現跨域請求還可以通過DOM中一些自帶的功能,畢竟這樣可以省去修改服務器代碼的時間。

  • 圖像Ping

通過<img>標籤可以直接加載其它網站上的圖片,而不用擔心跨不跨域的問題,它自己帶有onload和onerror事件,可以用於處理響應成功和失敗事件。

var img = new Image()
img.src = "https://pic4.zhimg.com/v2-de96b4afbe2140ace8c268bb60112283_b.jpg"

但是這種方法有兩個缺點,一是隻能使用GET請求,二是無法訪問服務器的文本

  • JSONP

JSONP是JSON with padding的簡寫,它是通過動態調用<script>標籤來使用的。

script.src = url

一個JSONP分為兩個部分,一是回調函數,一般回調函數的名字是要寫在請求地址中的,二是從服務器傳回來的JSON數據。

callback({ "name" : "Nicholas" })

而一個JSONP請求長這樣:

http://freegeoip.net/json/?callback=handleReponse

一個正常的JSONP請求長這樣:

function handleResponse(response){
    alert(response.city)
}

var script = document.createElement("script")
script.src = "http://freegeoip.net/json/?callback=handleReponse"
  • Comet

Comet與Ajax是彼此相反的,Comet是從服務器向頁面推送數據的技術。

有兩種方式可以實現,長輪詢和HTTP流

長輪詢的意思頁面發送一個請求,與服務器建立好鏈接並保持打開的狀態,當服務器有數據可發送時才發送

HTTP流的意思是只發送一次請求,建立好連接之後服務器會週期性地發送消息。在頁面的實現上可以通過檢測readyState,因為週期性發送會使readyState週期性地變為3,每次變為3的時候將新數據接到已接收的數據上。

  • SSE

管理Comet的時候很容易出錯,同時為了簡化這一技術,Comet就被賦予了兩個接口,SSE和Web Socket。

SSE指的是服務器發送事件,用於建立與服務器的單向連接,使用時需要先創建一個EventScore對象,它擁有3個事件。

open:建立連接時觸發
message:從服務器接收到新事件時觸發
error:無法建立連接時觸發

在message事件中通過event.data可以獲取到響應數據

source.onmessage = function (event){
    return event.data;
}

對於Comet的事件流,SSE通過將Http的響應MIME類型設置為text/event-stream,處理如下:

data: bar
data: foo

該響應事件收到的消息值為barnfoo。另外可以通過為事件設置id來進行標記

data:bar
id:1

在連接中斷的時候,EventSource對象會把Last-Event-ID放入到Http的頭部裏,併發往服務器,這樣服務器就知道是從哪個事件中斷的了。

  • Web Sockets

Web Sockets的目標在於為一個單獨的持久連接上提供全雙工、雙向的通信服務。創建Web Sockets之後,會先發送一個Http請求已建立連接,然後再將Http連接升級成Web Socket協議。

也就是説Web Sockets使用了自定義的協議,由http://變成了ws://,https://變成了wss://

這個協議的好處就在於可以支持客户端與服務器之間比較少量的通信,而不用承擔Http協議那種字節級別的開銷。

使用Web Socket的時候,首先先建立一個WebSocket對象並傳入要連接的URL

var socket = new WebSocket(url)

建立對象之後,瀏覽器馬上就會嘗試創建新的連接,WebSocket擁有與XHR類似的readyState屬性,不過不太一樣,而且它沒有readystateChange事件,但是其是用其它事件來對應不同狀態。

分別是:
open:在成功連接時觸發
error:在連接發生錯誤時觸發
close:在連接關閉時觸發

而其中只有close事件是有額外的信息,即:wasClean、code和reason,其中wasClean是一個布爾值,表示連接是否成功關閉;code表示服務器返回的狀態碼;reason表示服務器發回的信息。

socket.onclose = function (event){
    alert("Was Clean?"+ event.wasClean "Code = " + event.code + "Reason = " + event.reason
}

因為其是雙向通信,所以發送與接收數據的方法如下:

//發送
socket.send("Hello World")

//接收
socket.onmessage = function (event){
  var data = event.data
}
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.