Ajax
技術的核心是XMLHttpRequest
對象。咱們使用XMLHttpRequest
對象來發送一個Ajax
請求。這是由微軟首先引入的一個特性,其餘瀏覽器提供商後來都提供了相同的實現。javascript
XMLHttpRequest
已經獲得普遍接受,後來W3C
對它進行了標準化,提出了XMLHttpRequest
標準。XMLHttpRequest
標準又分爲Level 1
和Level 2
。html
並不是全部瀏覽器都完整地實現了XMLHttpRequest 2級
規範,但全部瀏覽器都實現了它規定的部份內容。html5
XMLHttpRequest Level 1
主要存在如下缺點:java
Level 2
對Level 1
進行了改進,XMLHttpRequest Level 2
中新增瞭如下功能:ajax
formData
對象,支持發送表單數據。下面的一行代碼就能夠建立XMLHttpRequest
對象。json
const xhr = new XMLHttpRequest()
複製代碼
兼容性查詢:caniuse.com/#search=XML…segmentfault
Accept
:客戶端能夠處理的內容類型。好比:Accept: */*
。Accept-Charset
:客戶端能夠處理的字符集類型。好比:Accept-Charset: utf8
。Accept-Encoding
:客戶端能夠處理的壓縮編碼。好比:Accept-Encoding: gzip, deflate, br
。Accept-Language
:客戶端當前設置的語言。好比:Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
。Connection
:客服端與服務器之間鏈接的類型。好比:Connection: keep-alive
。Cookie
:當前頁面設置的任何Cookie
。Host
:發出請求頁面所在的域。Referer
:表示當前請求頁面的來源頁面的地址,即當前頁面是經過此來源頁面裏的連接進入的。User-Agent
:客戶端的用戶代理字符串。通常包含瀏覽器、瀏覽器內核和操做系統的版本型號信息。Content-Type
:客戶端告訴服務器實際發送的數據類型。好比:Content-Type: application/x-www-form-urlencoded
。open()
方法用於初始化一個請求。跨域
open()
方法接收三個參數:數組
method
:要發送的請求的類型。好比GET
、POST
、PUT
、DELETE
等。url
:請求的URL
。async
:是否異步發送請求的布爾值。true
爲異步發送請求。const xhr = new XMLHttpRequest()
xhr.open('get', '/userInfo', true)
複製代碼
調用open()
方法並不會真正發送請求,而只是啓動一個請求以備發送。
send()
方法用於發送HTTP
請求。
send()
方法接收一個參數:
data
:做爲請求主體發送的數據。若是不須要經過請求主體發送數據,則必須傳入null
。該參數能夠接收字符串、FormData
、Blob
、ArrayBuffer
等類型。const xhr = new XMLHttpRequest()
xhr.send(null)
複製代碼
setRequestHeader()
方法能夠設置自定義的請求頭部信息。
setRequestHeader()
方法接收二個參數:
header
:頭部字段的名稱。value
:頭部字段的值。要成功發送請求頭部信息,此方法必須在open()
和send()
之間調用。
const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
xhr.setRequestHeader('MyHeader', 'MyValue')
xmlhttp.send()
複製代碼
readyState
屬性表示請求/響應過程的當前活動階段。這個屬性的值以下:
UNSENT
)未初始化。還沒有調用open()
方法。OPENED
)啓動。已經調用open()
方法,但沒有調用send()
方法。HEADERS_RECEIVED
)發送。已經調用send()
方法,但還沒有接收到響應。LOADING
)接收。已經接收到部分響應數據。DONE
)完成。已經接收到所有響應數據。只要readyState
屬性的值發生變化,都會觸發一次onreadystatechange
事件。利用這個事件來檢測每次狀態變化後readyState
的值。通常狀況下只對readyState
值爲4
的階段作處理,這時全部數據都已經就緒。
const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
xhr.onreadystatechange = function () {
if(xhr.readyState !== 4) {
return
}
if(xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.responseText)
}
}
xhr.send(null)
複製代碼
timeout
屬性表示請求在等待響應多少毫秒以後就終止。若是在規定的時間內瀏覽器尚未接收到響應,就會觸發ontimeout
事件處理程序。
const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
//將超時設置爲3秒鐘
xhr.timeout = 3000
// 請求超時後請求自動終止,會調用 ontimeout 事件處理程序
xhr.ontimeout = function(){
console.log('請求超時了')
}
xhr.send(null)
複製代碼
overrideMimeType()
方法可以重寫服務器返回的MIME
類型,從而讓瀏覽器進行不同的處理。
假如服務器返回的數據類型是text/xml
,因爲種種緣由瀏覽器解析不成功報錯,這時就拿不到數據。爲了拿到原始數據,咱們能夠把MIME
類型改爲text/plain
,這樣瀏覽器就不會去自動解析,從而咱們就能夠拿到原始文本了。
const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
xhr.overrideMimeType('text/plain')
xhr.send(null)
複製代碼
responseType
屬性是一個字符串,表示服務器返回數據的類型。使用xhr.response
屬性來接收。
這個屬性是可寫的,能夠在調用open()
方法以後,send()
方法以前設置這個屬性的值,告訴服務器返回指定類型的數據。若是responseType
設爲空字符串,等同於默認值text
。
responseType
屬性能夠設置的格式類型以下:
responseType 屬性的值 |
response 屬性的數據類型 |
說明 |
---|---|---|
"" |
String 字符串 |
默認值,等同於text (在不設置responseType 時) |
"text" |
String 字符串 |
服務器返回文本數據 |
"document" |
Document 對象 |
但願返回XML 格式數據時使用 |
"json" |
javaScript 對象 |
IE10/IE11 不支持 |
"blob" |
Blob 對象 |
服務器返回二進制對象 |
"arrayBuffer" |
ArrayBuffer 對象 |
服務器返回二進制數組 |
當將responseType
設置爲一個特定的類型時,你須要確保服務器所返回的類型和你所設置的返回值類型是兼容的。那麼若是二者類型不兼容,服務器返回的數據就會變成null
,即便服務器返回了數據。
給一個同步請求設置responseType
會拋出一個InvalidAccessError
的異常。
// 獲取一張圖片代碼示例
const xhr = new XMLHttpRequest()
xhr.open('get', '/server/image.png', true)
xhr.responseType = 'blob'
xhr.onload = function(e) {
if (xhr.status >= 200 && xhr.status < 300) {
const blob = this.response
// ...
}
}
xhr.send(null)
複製代碼
withCredentials
屬性是一個布爾值,表示跨域請求時是否協帶憑據信息(cookie
、HTTP
認證及客戶端SSL
證實等)。默認爲false
。
若是須要跨域Ajax
請求發送Cookie
,須要withCredentials
屬性設爲true
。若是在同域下配置xhr.withCredentials
,不管配置true
仍是false
,效果都會相同。
const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
xhr.withCredentials = true
xhr.send(null)
複製代碼
當配置了withCredentials
爲true
時,必須在後端增長response
頭信息Access-Control-Allow-Origin
,且必須指定域名,而不能指定爲*
。還要添加Access-Control-Allow-Credentials
這個頭信息爲true。
response.addHeader("Access-Control-Allow-Origin", "http://example.com")
response.addHeader("Access-Control-Allow-Credentials", "true")
複製代碼
在接收到響應以前調用abort()
方法用來取消異步請求。當一個請求被終止後,它的readyState
屬性將被置爲0
。在終止請求以後,還應該對XMLHttpRequeat
對象進行解引用操做。
當調用abort()
後,會觸發onabort
事件。
const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
xmlhttp.onabort = function () {
console.log('請求被停止')
}
xmlhttp.send()
// 將會調用咱們上面定義的 onabort 回調函數
xmlhttp.abort()
複製代碼
將查詢字符串參數追加到URL
的末尾,將信息發送給服務器。
GET
參數的編碼方式是沒法人爲干涉的,這致使了不一樣瀏覽器有不一樣的編碼方式,所以最穩妥的方案是人工預編碼,人工解碼,從而禁止瀏覽器編碼的干涉。
const xhr = new XMLHttpRequest()
// 使用encodeURIComponent()進行編碼
const tempParam = encodeURIComponent('age')
const tempValue = encodeURIComponent('20')
xhr.open('get', '/server?tempParam=tempValue&money=100', true)
複製代碼
POST
請求把數據做爲請求的主體(請求的body
)提交。下面是四種常見的POST
請求提交數據方式。
瀏覽器的原生<form>
表單,若是不設置enctype
屬性,那麼最終就會以application/x-www-form-urlencoded
方式提交數據。
表單上傳文件時,必須讓<form>
表單的enctype
等於multipart/form-data
。
當發送Ajax
請求時,把application/json
做爲請求頭,用來告訴服務端消息主體是序列化後的JSON
字符串。
使用HTTP
做爲傳輸協議,XML
做爲編碼方式的遠程調用規範。
將Content-Type
頭部信息設置爲application/x-www-form-urlencoded
。可使用XMLHttpRequest
對象來模仿表單提交。
const xhr = new XMLHttpRequest()
xhr.open('post', '/server', true)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
const form = document.getElementById('myForm')
// serialize()爲表單序列化方法
xhr.send(serialize(form))
複製代碼
也可使用XMLHttpRequest level 2
的FormData
來序列化表單數據。
const xhr = new XMLHttpRequest()
xhr.open('post', '/server', true)
const form = document.getElementById('myForm')
const formData = new FormData(form)
formData.append("id", "123456")
xhr.send(formData)
複製代碼
使用FormData
沒必要明確地在XMLHttpRequest
對象上設置請求頭部。XMLHttpRequest
對象可以識別傳入的數據類型是FormData
的實例,並配置適當的頭部信息。
在XMLHttpRequest
對象開始傳送數據時被觸發,也就是調用send()
方法(HTTP
請求發出)的時候。
xhr.onloadstart = function () {
console.log('開始發出請求...')
}
複製代碼
在接收響應期間持續不斷地觸發。
onprogress
事件處理程序會接收到一個event
對象,它的target
屬性是XMLHttpRequest
對象,而且event
包含着三個額外的屬性:loaded
、total
和lengthComputable
。
event.loaded
:已傳輸的數據量(已經接收的字節數)。event.total
:總共的數據量(根據Content-Length
響應頭部肯定的預期字節數)。event.lengthComputable
:進度信息是否可用的布爾值。有了這些信息,就能夠建立一個Ajax
請求進度條了。
const xhr = new XMLHttpRequest()
xhr.onprogress = function (event) {
if (!event.lengthComputable) {
return console.log('沒法計算進展')
}
const percentComplete = event.loaded / event.total * 100
console.log(`進度百分比:${percentComplete}%`)
}
xhr.open('post', '/server', true)
xhr.send(null)
複製代碼
在請求發生錯誤時觸發。只有發生了網絡層級別的異常纔會觸發此事件,對於應用層級別的異常,好比響應返回的statusCode
是4xx
時,並不屬於NetWork Error
,因此不會觸發onerror
事件,而是會觸發onload
事件。
xhr.onerror = function(e) {
console.log('數據接收出錯')
}
複製代碼
調用abort()
方法而終止請求時觸發。
當請求成功,接收到完整的響應數據時觸發。
可使用onload
事件能夠用來替代readystatechange
事件。由於響應接收完畢後將觸發onload
事件,所以也就沒有必要去檢查readyState
屬性了。只要瀏覽器接收到服務器的響應,無論其狀態如何,都會觸發load
事件。
const xhr = new XMLHttpRequest()
xhr.onload = function onload() {
console.log('數據接收完畢')
if(xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.responseText)
}
}
xhr.open('post', '/server', true)
xhr.send(formData)
複製代碼
爲確保正常執行,必須在調用open()
方法以前添加onprogress
事件處理程序。
在請求結束(包括請求成功和請求失敗),或者觸發error
、abort
或load
事件後觸發。
xhr.onloadend = function(e) {
console.log('請求結束,狀態未知')
}
複製代碼
每一個請求都從觸發
loadstart
事件開始,接下來是一或多個progress
事件,而後觸發error
、abort
或load
事件中的一個,最後以觸發loadend
事件結束。
XMLHttpRequest
不只能夠發送請求,還能夠發送文件,就是Ajax
文件上傳。
發送文件之後,經過XMLHttpRequest.upload
屬性能夠獲得一個XMLHttpRequestUpload
對象。經過這個對象,能夠得知上傳的進展。實現方案就是監聽這個對象的各類事件:onloadstart
、onprogress
、onabort
、onerror
、onload
、ontimeout
、onloadend
。
當文件上傳時,對upload
屬性指定progress
事件的監聽函數,可得到上傳的進度。
const xhr = new XMLHttpRequest()
if (xhr.upload) {
xhr.upload.onprogress = function progress(e) {
if (e.total > 0) {
e.percent = e.loaded / e.total * 100
}
}
}
複製代碼
在接收到響應後,第一步是檢查status
屬性。以肯定響應已經成功返回。將HTTP
狀態代碼爲200
做爲成功的標誌。狀態代碼爲304
表示請求的資源並無被修改,能夠直接使用瀏覽器中緩存的版本,也被認爲是有效的。
Content-Type
:服務器告訴客戶端響應內容的類型和採用字符編碼。好比:Content-Type: text/html; charset=utf-8
。Content-Length
:服務器告訴客戶端響應實體的大小。好比:Content-Length: 8368
。Content-Encoding
:服務器告訴客戶端返回的的壓縮編碼格式。好比:Content-Encoding: gzip, deflate, br
。status
屬性返回一個整數,表示服務器迴應的HTTP
狀態碼。若是服務器沒有返回狀態碼,那麼這個屬性默認是200
。請求發出以前,該屬性爲0
。該屬性只讀。
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 處理服務器的返回數據
}
}
複製代碼
statusText
屬性返回一個字符串,表示服務器發送的狀態說明。好比OK
和Not Found
。在請求發送以前,該屬性的值是空字符串。若是服務器沒有返回狀態提示,該屬性的值默認爲OK
。該屬性爲只讀屬性。
要經過檢測status
屬性來決定下一步的操做,不要依賴statusText
,由於statusText
在跨瀏覽器使用時不太可靠。
response
屬性表示服務器返回的數據。它能夠是任何數據類型,好比字符串、對象、二進制對象等等,具體的類型由XMLHttpRequest.responseType
屬性決定。該屬性只讀。
若是本次請求沒有成功或者數據不完整,該屬性等於null
。
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
console.log(xhr.response)
}
}
複製代碼
responseText
屬性返回從服務器接收到的字符串,該屬性爲只讀。
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 處理服務器的返回數據
console.log(xhr.responseText)
}
}
複製代碼
若是響應的內容類型是"text/xml"
或"application/xml"
,這個屬性中將保存包含着響應數據的HTML
或XML
文檔對象。該屬性是隻讀屬性。
不管內容類型是什麼,響應主體的內容都會保存到responseText屬性中。而對於非XML數據而言,responseXML屬性的值將爲null。
responseURL
屬性是字符串,表示發送數據的服務器的網址。若是URL
爲空則返回空字符串。若是URL
有錨點,則位於URL#
後面的內容會被刪除。若是服務器端發生跳轉,這個屬性返回最後實際返回數據的網址。
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://example.com/test', true)
xhr.onload = function () {
// 返回 http://example.com/test
console.log(xhr.responseURL)
}
xhr.send(null)
複製代碼
getResponseHeader()
方法返回HTTP
頭信息指定字段的值,若是尚未收到服務器的響應或者指定字段不存在,返回null
。該方法的參數不區分大小寫。
const xhr = new XMLHttpRequest()
xhr.onload = function onload() {
console.log(xhr.getResponseHeader('Content-Type'))
}
xhr.open('post', '/server', true)
xhr.send(null)
複製代碼
若是有多個字段同名,它們的值會被鏈接爲一個字符串,每一個字段之間使用逗號+空格分隔。
getAllResponseHeaders()
方法返回一個字符串,表示服務器發來的全部HTTP
頭信息。格式爲字符串,每一個頭信息之間使用CRLF
分隔(回車+換行),若是沒有收到服務器迴應,該屬性爲null
。若是發生網絡錯誤,該屬性爲空字符串。
const xhr = new XMLHttpRequest()
xhr.onload = function onload() {
const responseHeaders = 'getAllResponseHeaders' in xhr ? xhr.getResponseHeaders() : null
}
xhr.open('post', '/server', true)
xhr.send(null)
複製代碼
上面代碼用於獲取服務器返回的全部頭信息。返回值多是下面這樣的字符串。
content-encoding: gzip\r\n
content-length: 2020\r\n
content-type: text/html; charset=utf-8\r\n
複製代碼
須要對這個字符串進行處理才能正確使用。
const str = 'date: Fri, 08 Dec 2017 21:04:30 GMT\r\n'
+ 'content-encoding: gzip\r\n'
function trim(str) {
return str.replace(/^\s*/, '').replace(/\s*$/, '')
}
function parseHeaders(headers) {
if (!headers) {
return {}
}
const parsed = {}
let key, val, i
const arr = headers.split(/[\r\n]+/)
arr.forEach((line) => {
i = line.indexOf(':')
key = trim(line.substr(0, i)).toLowerCase()
val = trim(line.substr(i + 1))
if (key) {
parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val
}
})
return parsed
}
//{date: "Fri, 08 Dec 2017 21:04:30 GMT", content-encoding: "gzip"}
console.log(parseHeaders(str))
複製代碼