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

Ajax的全稱就是Asynchronous JavaScript + XML,它的本意是爲了能夠在不刷新頁面的狀況下異步更新數據。php

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

雖然它的名稱中包含有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對象的屬性,經常使用到的屬性有如下幾個:app

  • 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
}
相關文章
相關標籤/搜索