JavaScript中的AJAX

東東哥發表於2016-05-12

XMLHttpRequest物件

IE7+,FireFox,Chrome,Opera,Safari建立XHR物件

建立XHR物件的相容性寫法

XHR用法

傳送同步請求

使用 XHR 時,首先要呼叫 open() 方法,傳遞三個引數:

  1. 要傳送的請求型別( get , post 等)
  2. 請求的 url
  3. 是否非同步傳送

要傳送特定的請求,必需像下面這樣呼叫 send() 方法

這裡 send() 方法接收一個引數,作為請求主體傳送的資料。如果不需要通過請求主體傳送資料,這裡必須傳入 null ,因為這個引數對有些瀏覽器來說是必需的。呼叫 send() 之後,請求就會被分派到伺服器。
由於這次請求是同步的,JavaScript 程式碼會等到伺服器響應之後再繼續執行。在收到響應之後,相應的資料會自動填充XHR物件的屬性,相關的屬性簡介如下:

  • responseText: 作為響應主體被返回的文字。
  • responseXML: 如果響應的內容型別是 “text/xml”或”application/xml”,這個屬性中將儲存包含著相應資料的XML DOM文件。
  • status: 響應的HTTP狀態。
  • statusText: HTTP狀態的說明。

接受響應之後,第一步是檢查 status 屬性,以確定響應已經成功返回。狀態碼:

  • 200 表示成功
  • 304 表示請求的資源並沒有修改,可以直接使用瀏覽器中快取的版本,響應也是有效的

像下面這樣檢查上述這兩種狀態碼的狀態:

注意:無論內容型別是什麼,響應主體的內容都會儲存到 responseText 屬性中;而對於非 XML 資料而言, responseXML 屬性的值將為 null。

傳送非同步請求

向前面這樣傳送同步請求當然沒問題,但多數情況下,我們還是要傳送非同步請求,才能讓 JavaScript 繼續執行而不必等待響應。此時,可以檢測 XHR 物件的 readyState 屬性,該屬性表示請求/響應過程中的當前活動階段。這個屬性可取的值如下:

  • 0:未初始化。尚未呼叫 open() 方法。
  • 1:啟動。已經呼叫 open() 方法,但尚未呼叫 send() 方法。
  • 2:傳送。已經呼叫 send() 方法,但尚未接收響應。
  • 3:接收。已經接收到部分響應資料。
  • 4:完成。已經接收到全部響應資料,而且已經可以在客戶端使用了。

只要 readyState 屬性的值由一個值變為另一個值,都會觸發一次 readystatechange 事件。可以利用這個事件來檢測每次狀態變化後的 readyState 的值,通常,我們只對 readyState 值為 4 的階段感興趣,因為這時所有的資料都已經就緒。不過,必須在呼叫 open() 之前指定 onreadyState 事件處理程式才能確保跨瀏覽器相容性。例子如下:

另外,在接收到響應之前還可以呼叫 abort() 方法來取消非同步請求,如下所示:

呼叫這個方法後,XHR 物件會停止觸發事件,而且也不再允許訪問任何與響應有關的物件屬性。

HTTP頭部資訊

每個 HTTP 請求和響應都會帶有響應的頭部資訊,有的對開發人員有用,有的也沒有什麼用,XHR 物件也提供了操作這兩種頭部(即請求頭部和響應頭部)資訊的方法。
預設情況下,在傳送 XHR 請求的同事,還會傳送下列頭部資訊。

  • Accept: 瀏覽器能夠處理的內容型別。
  • Accept-Charset: 瀏覽器能夠顯示的字符集。
  • Accept-Encoding: 瀏覽器能夠處理的壓縮編碼。
  • Accept-Language: 瀏覽器當前設定的語言。
  • Connection: 瀏覽器與伺服器之間連線的型別。
  • Cookie: 當前頁面設定的語言。
  • Host: 發出請求的頁面所在的域。
  • Referer: 發出請求的頁面的URI。
  • User-Agent: 瀏覽器的使用者代理字串。

使用 setRequestHeader() 方法可以設定自定義的請求頭部資訊。這個方法接受兩個引數:頭部欄位的名稱和頭部欄位的值。要成功傳送請求頭部資訊,必須在呼叫 open() 方法之後且呼叫 send() 方法之前呼叫 setRequestHeader(),如下面的例子所示。

伺服器在接收到這種自定義的頭部資訊之後,可移植性響應的後續操作。建議使用自定義的頭部欄位名稱,不要使用瀏覽器正常傳送的欄位名稱

呼叫 XHR 物件的 getResponseHeader() 方法並傳入頭部欄位名稱,可以取得相應的響應頭部資訊。而呼叫 getAllResponseHeaders() 方法可以取得一個包含所有頭部資訊的長字串。看下面的例子:

GET請求

GET 是最常見的請求型別,最常用於向伺服器查詢某些資訊。必要時,可以講查詢字串引數追加到 URL 的末尾,以便將資訊傳送給伺服器。對 XHR 而言,位於傳入 open() 方法的 URL 末尾的查詢字串必須經過正確的編碼才行。
使用 GET 請求經常會發生的一個錯誤,及時查詢字串的格式有問題。查詢字串中每個引數的名稱和值必須使用 encodeURIComponent() 進行編碼,然後才能放到 URL 的末尾;而且所有名-值對都必須由和號(&)分隔,例子如下:

下面這個函式可以輔助向現有 URL 的末尾新增查詢字串引數:

使用方法:

POST請求

POST 請求通常用於向伺服器傳送應該被儲存的資料。POST 請求應該吧資料作為請求的主體提交,而 GET 請求傳統上不是這樣。
預設情況下,伺服器對 POST 請求和提交 Web 表單的請求並不會一視同仁。因此,伺服器端必須有程式來讀取傳送過來的原始資料,並從中解析出有用的部分。不過,我們可以使用 XHR 來模仿表單提交:首先將 Content-Type 頭部資訊設定為 application/x-www-from-urlencoded,也就是表單提交時的內容型別,其次是以適當的格式建立一個字串。
如下所示:

這個函式可以將 ID 為 “user-info” 的表單中的資料序列化之後傳送給伺服器。

XMLHttpRequest2級

XMLHttpRequest 1級只是把已有的 XHR 物件的實現細節描述了出來。而 XMLHttpRequest2級則進一步發展了 XHR。並非所有瀏覽器都完整地實現了 XMLHttpRequest2級規範。

FormData

現代 Web 應用中頻繁使用的一項功能就是表單資料的序列化,XMLHttpRequest2級為此定義了 FormData 型別。FormData 為序列化表單以及建立與表單格式相同的資料提供了便利。下面程式碼建立了 FormData 物件,並向其中新增了一些資料。

這個 append() 方法接受兩個引數:鍵和值,分別對應表單欄位的名字和欄位中包含的值。可以像這樣新增任意多的鍵值對。而通過向 FormData 建構函式中傳入表單元素,也可以用表單元素的資料預先向其中填入鍵值對:

建立了 FormData 的例項後,可以將它直接傳給 XHR 的 send() 方法,如下所示:

使用 FormData 的方便之處在於不必明確地在 XHR 物件上設定請求頭部。XHR 物件能夠識別傳入的資料型別是 FormData 的例項,並配置適當的頭部資訊。
支援 FormData 的瀏覽器有 Firefox4+,Safari5+,Chrome和Android3+版WebKit。

跨域資源共享

通過 XHR 實現 Ajax 通訊的一個主要限制,來源於跨域安全策略。
CORS(Cross-Origin Resource Sharing,跨域資源共享)背後的基本思想,及時使用自定義的HTTP頭部讓瀏覽器與伺服器進行溝通,從而決定請求或響應是應該成功,還是應該失敗。
比如一個簡單地使用 GET 或 POST 傳送的請求,它沒有自定義的頭部,而主題內容是 text/plain。在傳送該請求時,需要給它附加一個額外的 Origin 頭部,其中包括請求頁面的源資訊(協議、域名、埠),以便伺服器根據這個頭部資訊來決定是否給予響應,下面是 Oringin 頭部的一個示例:

如果伺服器認為這個請求可以接受,就在 Access-Control-Allow-Origin 頭部中回發相同的源資訊(如果是公共資源,可以回發 “*”)。例如:

如果沒有這個頭部,或者有這個頭部但源資訊不匹配,瀏覽器就會駁回請求。正常情況下,瀏覽器會處理請求。注意,請求和響應都不包含 cookie 資訊。

IE 對 CORS 的實現

微軟在 IE8 中引入了 XDR (XDomainRequest) 型別。這個物件與 XHR 類似,但能實現安全可靠的跨域通訊。XDR 物件的安全機制部分實現了 W3C 的 CORS 規範。以下是 XDR 與 XHR 的一些不同之處。

  • cookie 不會隨請求傳送,也不會隨響應返回。
  • 只能設定請求頭部資訊的 Content-Type 欄位。
  • 不能訪問響應頭部資訊。
  • 只支援GET和POST請求。

所有的 XDR 請求都是非同步執行的,不能用它來建立同步請求。請求返回之後,會觸發 load 事件,響應的資料也會儲存在 responseText 屬性中。

在接收到響應後,你只能訪問響應的原始文字;沒有辦法確定響應的狀態程式碼。而且,只要響應有效就會觸發 load 事件,如果失敗(包括響應中缺少 Access-Control-Allow-Origin頭部),就會觸發 error 事件。遺憾的是,除了錯誤本身之外,沒有其他資訊可用,因此唯一能夠確定的就只有請求未成功了。要檢測錯誤,可以像下面這樣指定一個 onerror 事件處理程式。

為支援 POST 請求,XDR 物件提供了 contentType 屬性,用來表示傳送資料的格式,如下所示:

這個屬性通過 XDR 物件影響頭部資訊的唯一方式。

其他瀏覽器對 CORS 的實現

Forefox3.5+,Sarari4+,Chrome,iOS 版 Sarari 和 Android 平臺中的 WebKit 都通過 XMLHttpRequest 物件實現了對 CORS 的原生支援。在嘗試開啟不同來源的資源時,無需額外編寫程式碼就可以出發這個行為。要請求位於另一個域中的資源,使用標準的 XHR 物件並在 open() 方法中傳入絕對 URL 即可,例如:

與 IE 中的 XDR 物件不同,通過跨域 XHR 物件可以訪問 status 和 statusText 屬性,而且還支援同步請求,跨域 XHR 物件也有一些限制,但為了安全,這些限制是必需的,一下就是這些限制。

  • 不能使用 setRequestHeader() 設定自定義頭部。
  • 不能傳送和接收 cookie。
  • 呼叫 getAllResponseHeaders() 方法總會返回空字串。

對於本地資源,最好使用相對 RUL,在訪問遠端資源時再使用絕對 URL。

Preflighted Requests

CORS 通過一種叫做 Preflighted Requests 的透明伺服器驗證機制支援開發人員使用自定義的頭部,GET 或 POST 之外的方法,以及不同型別的主體內容,在使用下列高階選項來傳送請求時,就會像伺服器傳送一個 Preflight 請求。這種請求使用 OPTIONS 方法,傳送下列頭部。

  • Origin: 與簡單的請求相同。
  • Access-Control-Request-Method: 請求自身使用的方法。
  • Access-Control-Request-Headers: (可選) 自定義的頭部資訊,多個頭部以逗號分隔。

以下是一個帶有自定義頭部的 NCZ 的使用 POST 方法傳送的請求。

傳送這個請求後,伺服器可以決定是否允許這種型別的請求。伺服器通過在響應中傳送如下頭部與瀏覽器進行溝通。

例如:

Preflight 請求結束後,結果將按照響應中指定的事件快取起來。而為此付出的代價只是第一次傳送這種請求時會多一次 HTTP 請求。

支援 Preflight 請求的瀏覽器包括 Firefox3.5+, Sarari4+ 和 Chrome。IE10 及更早版本都不支援。

帶憑據的請求(Requests with Credential)

預設情況下,跨域請求不提供憑據(Cookie、HTTP 認證及客戶端 SSL 證明等)。通過將 withCredentials 屬性設定為 true,可以指定某個請求應該傳送憑據。如果伺服器接收帶憑據的請求,會用下面的 HTTP 頭部來響應。

如果傳送的是帶憑據的請求,單伺服器的響應中沒有包含這個頭部,那麼瀏覽器就不會把響應交給 JavaScript (於是,responseText 中將是空字串,status 的值為0,而且會呼叫 onerror() 事件處理程式)。

支援 withCredentials 屬性的瀏覽器有 Firefox3.5+,Sarari4+ 和 Chrome。IE10 及更早版本都不支援。

下面這篇文章講的特別好,介紹了簡單地跨域請求、Preflight 請求和帶憑據的請求三種請求的區別和請求流程。
文章地址:http://www.cnblogs.com/loveis715/p/4592246.html

跨瀏覽器的 CORS (相容性 CORS的寫法)

即使瀏覽器對 CORS 的支援程度並不都一樣,但所有瀏覽器都支援簡單地(非 Preflight 和不帶憑據的)請求,因此有必要實現一個跨瀏覽器的方案。檢測 XHR 是否支援 CORS 的最簡單的方式,就是檢查是否存在 withCredentials 屬性,再結合檢測 XDomainRequest 物件是否存在,就可以兼顧所有瀏覽器了。

其他跨域技術

JSONP

JSONP 原理:JSONP 是通過動態script元素來使用的,使用時可以作為 src 屬性指定一個跨域 URL。 這裡的script元素有能力不受限制地從其他域載入資源。因為 JSONP 是有效的 JavaScript 程式碼,所以在請求完成後,即在 JSONP 響應載入到頁面中以後,就會立即執行。來看一個例子。

下面這篇文章介紹了 JSON 和 JSONP,值得一看
文章地址:http://kb.cnblogs.com/page/139725/

其他跨域

其他跨域技術還有 Comet、SSE、WebSockets等。感興趣的讀者可以查閱相關資料進行了解。

相關文章