深刻理解XMLHttpRequest

1、XMLHttpRequest的發展歷程

Ajax技術的核心是XMLHttpRequest對象。咱們使用XMLHttpRequest對象來發送一個Ajax請求。這是由微軟首先引入的一個特性,其餘瀏覽器提供商後來都提供了相同的實現。javascript

XMLHttpRequest已經獲得普遍接受,後來W3C對它進行了標準化,提出了XMLHttpRequest標準。XMLHttpRequest標準又分爲Level 1Level 2html

並不是全部瀏覽器都完整地實現了XMLHttpRequest 2級規範,但全部瀏覽器都實現了它規定的部份內容。html5

XMLHttpRequest Level 1主要存在如下缺點:java

  1. 不能發送二進制文件(如圖片、視頻、音頻等),只能發送純文本數據。
  2. 在發送和獲取數據的過程當中,沒法實時獲取進度信息,只能判斷是否完成。
  3. 受同源策略的限制,不能發送跨域請求。

Level 2Level 1進行了改進,XMLHttpRequest Level 2中新增瞭如下功能:ajax

  1. 在服務端容許的狀況下,能夠發送跨域請求。
  2. 支持發送和接收二進制數據。
  3. 新增formData對象,支持發送表單數據。
  4. 發送和獲取數據時,能夠獲取進度信息。
  5. 能夠設置請求的超時時間。

下面的一行代碼就能夠建立XMLHttpRequest對象。json

const xhr = new XMLHttpRequest()
複製代碼

兼容性查詢:caniuse.com/#search=XML…segmentfault

2、XMLHttpRequest對象發送請求相關API

請求頭相關

  • 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

更多參考:developer.mozilla.org/zh-CN/docs/…後端

open()方法

open()方法用於初始化一個請求。跨域

open()方法接收三個參數:數組

  1. 第一個參數 method:要發送的請求的類型。好比GETPOSTPUTDELETE等。
  2. 第二個參數 url:請求的URL
  3. 第三個參數 async:是否異步發送請求的布爾值。true爲異步發送請求。
const xhr = new XMLHttpRequest()
xhr.open('get', '/userInfo', true)
複製代碼

調用open()方法並不會真正發送請求,而只是啓動一個請求以備發送。

send()方法

send()方法用於發送HTTP請求。

send()方法接收一個參數:

  1. 第一個參數data:做爲請求主體發送的數據。若是不須要經過請求主體發送數據,則必須傳入null。該參數能夠接收字符串、FormDataBlobArrayBuffer等類型。
const xhr = new XMLHttpRequest()
xhr.send(null)
複製代碼

setRequestHeader()方法

setRequestHeader()方法能夠設置自定義的請求頭部信息。

setRequestHeader()方法接收二個參數:

  1. 第一個參數 header:頭部字段的名稱。
  2. 第二個參數 value:頭部字段的值。

要成功發送請求頭部信息,此方法必須在open()send()之間調用。

const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
xhr.setRequestHeader('MyHeader', 'MyValue')
xmlhttp.send()
複製代碼

readyState屬性和onreadystatechange事件

readyState屬性表示請求/響應過程的當前活動階段。這個屬性的值以下:

  • 0(UNSENT)未初始化。還沒有調用open()方法。
  • 1(OPENED)啓動。已經調用open()方法,但沒有調用send()方法。
  • 2(HEADERS_RECEIVED)發送。已經調用send()方法,但還沒有接收到響應。
  • 3(LOADING)接收。已經接收到部分響應數據。
  • 4(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事件

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()方法

overrideMimeType()方法可以重寫服務器返回的MIME類型,從而讓瀏覽器進行不同的處理。

假如服務器返回的數據類型是text/xml,因爲種種緣由瀏覽器解析不成功報錯,這時就拿不到數據。爲了拿到原始數據,咱們能夠把MIME類型改爲text/plain,這樣瀏覽器就不會去自動解析,從而咱們就能夠拿到原始文本了。

const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
xhr.overrideMimeType('text/plain')
xhr.send(null)
複製代碼

responseType屬性

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屬性

withCredentials屬性是一個布爾值,表示跨域請求時是否協帶憑據信息(cookieHTTP認證及客戶端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)
複製代碼

當配置了withCredentialstrue時,必須在後端增長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()方法和onabort事件

在接收到響應以前調用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()
複製代碼

GET請求

將查詢字符串參數追加到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請求

POST請求把數據做爲請求的主體(請求的body)提交。下面是四種常見的POST請求提交數據方式。

application/x-www-form-urlencoded

瀏覽器的原生<form>表單,若是不設置enctype屬性,那麼最終就會以application/x-www-form-urlencoded方式提交數據。

multipart/form-data

表單上傳文件時,必須讓<form>表單的enctype等於multipart/form-data

application/json

當發送Ajax請求時,把application/json做爲請求頭,用來告訴服務端消息主體是序列化後的JSON字符串。

text/xml

使用HTTP做爲傳輸協議,XML做爲編碼方式的遠程調用規範。

使用XMLHttpRequest模擬表單提交

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 2FormData來序列化表單數據。

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進度事件相關API

onloadstart

XMLHttpRequest對象開始傳送數據時被觸發,也就是調用send()方法(HTTP 請求發出)的時候。

xhr.onloadstart = function () {
  console.log('開始發出請求...')
}
複製代碼

onprogress

在接收響應期間持續不斷地觸發。

onprogress事件處理程序會接收到一個event對象,它的target屬性是XMLHttpRequest對象,而且event包含着三個額外的屬性:loadedtotallengthComputable

  1. event.loaded:已傳輸的數據量(已經接收的字節數)。
  2. event.total:總共的數據量(根據Content-Length響應頭部肯定的預期字節數)。
  3. 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)
複製代碼

onerror

在請求發生錯誤時觸發。只有發生了網絡層級別的異常纔會觸發此事件,對於應用層級別的異常,好比響應返回的statusCode4xx時,並不屬於NetWork Error,因此不會觸發onerror事件,而是會觸發onload事件。

xhr.onerror = function(e) {
 console.log('數據接收出錯')
}
複製代碼

onabort

調用abort()方法而終止請求時觸發。

onload

當請求成功,接收到完整的響應數據時觸發。

可使用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事件處理程序。

onloadend

在請求結束(包括請求成功和請求失敗),或者觸發errorabortload事件後觸發。

xhr.onloadend = function(e) {
  console.log('請求結束,狀態未知')
}
複製代碼

每一個請求都從觸發loadstart事件開始,接下來是一或多個progress事件,而後觸發errorabortload事件中的一個,最後以觸發loadend事件結束。

upload屬性

XMLHttpRequest不只能夠發送請求,還能夠發送文件,就是Ajax文件上傳。

發送文件之後,經過XMLHttpRequest.upload屬性能夠獲得一個XMLHttpRequestUpload對象。經過這個對象,能夠得知上傳的進展。實現方案就是監聽這個對象的各類事件:onloadstartonprogressonabortonerroronloadontimeoutonloadend

當文件上傳時,對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
        }
    }
}
複製代碼

3、XMLHttpRequest對象接收響應相關API

在接收到響應後,第一步是檢查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

更多參考:developer.mozilla.org/zh-CN/docs/…

status屬性

status屬性返回一個整數,表示服務器迴應的HTTP狀態碼。若是服務器沒有返回狀態碼,那麼這個屬性默認是200。請求發出以前,該屬性爲0。該屬性只讀。

if (xhr.readyState === 4) {
  if (xhr.status >= 200 && xhr.status < 300) {
    // 處理服務器的返回數據
  }
}
複製代碼

statusText屬性

statusText屬性返回一個字符串,表示服務器發送的狀態說明。好比OKNot Found。在請求發送以前,該屬性的值是空字符串。若是服務器沒有返回狀態提示,該屬性的值默認爲OK。該屬性爲只讀屬性。

要經過檢測status屬性來決定下一步的操做,不要依賴statusText,由於statusText在跨瀏覽器使用時不太可靠。

response屬性

response屬性表示服務器返回的數據。它能夠是任何數據類型,好比字符串、對象、二進制對象等等,具體的類型由XMLHttpRequest.responseType屬性決定。該屬性只讀。

若是本次請求沒有成功或者數據不完整,該屬性等於null

const xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4) {
    console.log(xhr.response)
  }
}
複製代碼

responseText屬性

responseText屬性返回從服務器接收到的字符串,該屬性爲只讀。

if (xhr.readyState === 4) {
  if (xhr.status >= 200 && xhr.status < 300) {
    // 處理服務器的返回數據
    console.log(xhr.responseText)
  }
}
複製代碼

responseXML屬性

若是響應的內容類型是"text/xml""application/xml",這個屬性中將保存包含着響應數據的HTMLXML文檔對象。該屬性是隻讀屬性。

不管內容類型是什麼,響應主體的內容都會保存到responseText屬性中。而對於非XML數據而言,responseXML屬性的值將爲null。

responseURL屬性

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()方法

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()方法

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))
複製代碼

參考文章

book.douban.com/subject/105…

juejin.im/post/5ab341…

segmentfault.com/a/119000000…

www.html5rocks.com/zh/tutorial…

imququ.com/post/four-w…

www.zhihu.com/question/28…

javascript.ruanyifeng.com/bom/ajax.ht…

相關文章
相關標籤/搜索