深刻了解XMLHttpRequest

XMLHttpRequest

瀏覽器在 XMLHttpRequest 類上定義了他們的 HTTP API。這個類的每個實例都表示一個獨立的請求/響應,而且這個對象的屬性和方法容許指定請求細節和提取響應數據。W3C在 XMLHttpRequest 規範的基礎上制定了2級 XMLHttpRequest (下文簡稱 XHR2 )標準草案,且大部分瀏覽器都已經支持了。javascript

在介紹 XMLHttpRequest 以前,我想先簡單說一下HTTP請求組成部分和響應的組成部分

一個 HTTP 請求由4部分組成:

  • HTTP 請求方法或‘動做’。
  • 正在請求的URL。
  • 一個可選的請求頭集合,其中可能包括身份驗證信息。
  • 一個可選的請求主體。

服務器返回的 HTTP 響應包含3部分:

  • 一個數字或文字組成的狀態碼,用來顯示請求的成功或失敗。
  • 一個響應頭集合。
  • 響應主體。

HTTP 請求的各部分有指定的順序:請求方法和URL首先到達,而後是請求頭,最後是主體。XHMLHttpRequest 實現一般直到調用send()方法纔開始啓動網絡。但 XMLHttpRequest API的設計彷佛使每一個方法都將寫入網絡流。這意味着調用 XMLHttpRequest 上的方法的順序必須匹配 HTTP 請求的架構。例如,setRequestHeader() 方法的調用必須在調用 open() 以前可是在 send() 以後,不然就會跑出異常。下面我門會按照這個順序來介紹。java

實例化

使用 XMLHttpRequest API的第一件事就是先實例化;程序員

var request = new XMLHttpRequest();
複製代碼

咱們也能重用已經存在的 XMLHttpRequest對象,可是這將會停止以前經過該對象發起的任何請求,但通常不會這麼用數據庫

指定請求

request.open('POST', url);
複製代碼
  • 第一個參數指定HTTP方法或動做,注意: 不區分大小寫,但一般都是使用大寫來匹配HTTP協議。json

    • GET 用於常規的請求,它適用於當URL徹底指定請求資源,當請求對服務器沒有任何反作用以及當服務器的響應是可緩存時。
    • POST 方法經常使用語HTML表單,他在請求主體中包含額外數據(表單數據)且這些數據場儲存到服務器上的數據庫中(反作用)。相同 URL 的重複請求從服務器獲得的相應數據可能不一樣。同時緩存時不該該使用這個請求。
  • 第二個參數是URL,這個URL能夠是相對url也能夠是絕對的url。跨域

  • 第三個參數用來設置請求的異步仍是同步瀏覽器

    • false表示同步
    • true表示異步,默認true
  • 若是請求一個受保護的URL,把用戶和密碼做爲第4和第5個參數傳遞。緩存

設置請求頭

request.setRequestheader('Content-Type': 'text/plain');
複製代碼

注意:

  • 若是對相同的頭調用 setRequestHeader() 屢次,新值不會取代以前指定的值,相反,HTTP請求將包含這個頭的多個副本或這個頭將指定多個值。安全

  • setRequestHeader() 沒有辦法設置下面的 headerXMLHttpRequest 將自動添加這些頭而防止僞造他們。相似的,XMLHttpRequest 對象自動處理 cookie,鏈接時間,字符集和編碼判斷,因此沒法向 setRequestHeader() 傳遞這些頭:服務器

Accept-Charset Accept-Encoding Connection Content-Length Cookie
Cookie2 Content-Transfer-Encoding Date Expect Host
Keep-Live Referer TE Trailer Transfer-Encoding
Upgrage User-Agent Via
  • send() 方法傳入 XML 文檔時,沒有指定 Content-TypeXMLHttpRequest 會自動設置一個合適的頭。相似的若是給 send() 傳入一個字符串但沒有指定 Content-Type,那麼 XMLHttpRequest 將會自動添加 text/plain; charset=utf-8 頭。

  • 當使用 GET 方法時,不須要調用 setRequestHeader() 這個方法,由於 GET 請求只能進行 url編碼(application/x-www-form-urlencoded),而若是使用 POST 方法且傳遞的參數是以 ‘&’ 和 ‘=’ 符號進行鍵值鏈接時,Content-Type 頭必須設置 application/x-www-form-urlencoded

發送請求

使用 XMLHttpRequest 發起HTTP請求的最後一步就是指定可選的請求主體並向服務器發送它:

request.send(null);
複製代碼
  • GET 請求絕對沒有主體,因此應該傳遞null或省略這個參數。
  • POST 請求一般擁有主體,同時它應該匹配使用 setRequestHeader() 指定的 Content-type 頭。

XMLHttpRequest 屬性

readystatechange

XMLHttpRequest 對象一般異步使用:發送請求後,send() 方法當即返回,直到響應返回。爲了在響應準備就緒的時候獲得通知,必須監聽 XMLHttpRequest 對象上的 readystatechange 事件。

readyState

它是一個整數, 他指定了 HTTP 請求的狀態。

  • 0: 初始化狀態。 xhr 對象已建立或已被 abort() 方法重置。
  • 1: open() 方法已調用,請求鏈接已經創建。可是 send() 方法未調用,請求數據未發送。
  • 2: send() 方法已調用,HTTP 請求已發送到 Web 服務器。接收到頭信息
  • 3: 全部響應頭部都已經接收到。響應體開始接收但未完成。
  • 4: HTTP 響應已經徹底接收

status

服務器返回的http狀態碼,當 readyState 小於 3 的時候讀取這一屬性會致使一個異常。

  • 200 表示成功
  • 404 表示'Not Found'錯誤

statusText

以數字和文字的形式返回 HTTP 狀態碼。

  • status === 200 statusText 爲 'OK'
  • status === 404 statusText 爲 'Not Found'

getRequestHeader()/getAllRequestHeaders()

使用這兩個方法均可以查詢到響應頭。XMLHttpRequest 會自動處理 cookie,他會從 getAllRequestHeaders() 頭返回集中過濾掉 cookie 頭。而若是給 getRequestHeader() 傳遞 Set-CookieSet-Cookie2 則會返回 null

responseText

responseText 接受到服務器的相應數據 返回的值是一個json字符串 經過 JSON.parse(xhr.responseText) 能夠獲得數據對象

  • readyState < 3 responseText 爲空字符串
  • readyState = 3 responseText 爲已經接收到數據部分
  • readyState = 4 responseText 爲接受到了全部的相應部分

responseXML

responseXML屬性能夠獲得 XML 的 Document形式的數據。

XHR2新增的事件集

XHR2規範定義了不少有用的事件集,在這個新的事件模型中,XMLHttpRequest 對象在請求的不一樣階段觸發不一樣類型的事件,因此它再也不須要檢查 readyState 屬性 。

  • onloadstart : 當調用 send() 時,觸發單個 loadstart 事件

  • onprogress : xhr對象會發生 progress 事件,一般每隔50ms左右觸發一次,因此可使用這個事件給用戶反饋請求的進度。若是請求快速完成,他可能不會觸發 progress 事件。注意這裏的 progress 是下載的進度,xhr2 額外的定義了上傳 upload 屬性,用來定義上傳的相關事件。

  • onload : 當事件完成時,觸發 load 事件,load 事件的處理程序應該檢查xhr對象的 status 狀態碼來肯定收到的是 200 仍是 404

  • ontimeout : 若是請求超時,會觸發 timeout 事件。

  • onerror : 大多重定向這樣的網絡錯誤會阻止請求的完成,但這些狀況發生時會觸發 error 事件。

  • onabort : 若是請求停止,會觸發 abort 事件。

  • onloadend : 對於任何具體的請求,瀏覽器將只會觸發 load/abort/timeout/error 事件中的一個。一旦這些事件觸發之後,瀏覽器將會觸發 loadend 事件。

注意: 上面的這些事件咱們能夠經過 xhr.addEventListener() 方法進行監聽。

progress事件中有三個屬性須要講解一下:

  • loaded : 目前傳輸的字節數值
  • total : 傳輸的數據的總體長度(單位字節),Content-Length == total,若是不知道內容的長度則 total == 0
  • lengthComputable : 若是知道內容的長度則 lengthComputable == true, 不然 false
xhr.onprogress = function(event) {
    if (event.lengthComputable) {
            
      const progress = event.loaded / event.total * 100;
    }
  }
複製代碼

XHR2 新增的 upload 屬性

XHR2 中新增了一個 upload 屬性,這個屬性值是一個對象,他定義了 addEventListener() 和 整個 progress 事件集合,好比說 onprogressonload。(但 upload 沒有定義 onreadystatechange 屬性,upload 僅能觸發新的事件類型)。

  • onloadstart : 和 XMLHttpRequest 中的 loadstart 事件同樣。

  • onprogress : 和 XMLHttpRequest 中的 progress 事件同樣。

  • onload : 和 XMLHttpRequest 中的 load 事件同樣。

  • ontimeout : 和 XMLHttpRequest 中的 timeout 事件同樣。

  • onerror : 和 XMLHttpRequest 中的 error 事件同樣。

  • onabort : 和 XMLHttpRequest 中的 abort 事件同樣。

  • onloadend : 和 XMLHttpRequest 中的 loadend 事件同樣。

注意

upload 屬性上定義的事件主要用在上傳文件時。咱們可使用 upload 上的 onloadstart,onprogress 分別監聽文件開始上傳和上傳過程當中進度的變化。

對於文件上傳,咱們如何設置請求頭??

const input = document.getElementsByTagName('input')[0];
  input.addEventListener('change', function() {
    var file = this.files[0];
    if (!file) return;
    var xhr = new XMLHttpRequest();
    xhr.open('POST', url);
    xhr.send(file);
  }, false);
複製代碼

文件類型是更普通的二進制大對象 Blob 類型中的一個字類型。XHR2 容許向 send() 方法傳入任何 Blob 對象。若是沒有顯示的設置 Content-Type 頭,這個 Blob 對象的 type 屬性用於設置待上傳的 Content-Type 頭。

multipart/form-data請求

XHR2 定義了新的 FormData API, 它容易實現多部分請求主體。首先,使用 FormData() 構造函數建立 FormData 對象,而後按需屢次調用這個對象的 append() 方法把個體部分(string/File/Blob對象) 添加到請求中。最後把 FormData 對象傳遞給 send() 方法。send() 方法將對請求定義合適的邊界字符串和設置 Content-Type 頭。

停止請求和超時

停止請求

經過調用 XMLHttpRequest 對象的 abort() 方法來取消正在進行的 HTTP 請求。當調用 abort() 方法後會觸發 xhr 對象的 onabort 事件。

超時

XHR2 定義了 timeout 屬性來指定請求自動終止的毫秒數。同時也定義了 timeout 事件,當超時發生時觸發。

demo

// 封裝一個request方法
  const request = (url, formData, cb) => {
    // 初始化
    const xhr = new XMLHttpRequest();
    // 定義請求的方法/動做和url
    xhr.open('POST', url);
    // 設置超時時間,單位是毫秒
    xhr.timeout = 2000; 

    xhr.ontimeout = function() {
      console.log('timeout');
    };
    // 開始上傳
    xhr.upload.onloadstart = function() {
      console.log('開始上傳');
    };
    // 上傳的進度
    xhr.upload.onprogress = function(event) {
      // 只有當 lengthComputable 爲true是,loaded 纔有值
      if (event.lengthComputable) {
        const value = Math.ceil((event.loaded / event.total) * 100);
        cb && cb({
          status: 'loading',
          progress: value,
          data: null,
        });
      }
    };
    // 監聽事件完成, 完成並不必定表明請求成功,因此須要判斷 status 狀態碼
    xhr.onload = function() {
      const resp: Response = {
        status: 'success',
        progress: 100,
        data: null,
      };
      if (xhr.status === 200) {
        resp.data = JSON.parse(xhr.responseText);
        cb && cb(resp);
      } else {
        resp.status = 'error';
        cb && cb(resp);
      }
    };
    xhr.onerror = function() {
      cb && cb({
        status: 'error',
        progress: 0,
        data: null,
      });
    };
    xhr.onabort = function() {
      console.log('onabort');
    };
    xhr.onloadend = function() {
      console.log('上傳結束');
    };
    xhr.send(formData);
    return xhr;
  };
複製代碼

HTTP跨域請求

做爲同源策略的一部分,XMLHttpRequest 對象一般僅能夠發起和文檔具備相同服務器的 HTTP 請求。這個限制關閉了安全裏漏洞,但同時也阻止了大量可以使用的跨域請求。好在 XHR2 經過在 HTTP 響應中選擇發送合適的 CORS(Cross-Origin Resource Sharing,跨域資源共享) 容許跨域訪問網站。在平常開發中使用跨域請求並不須要進行的額外的其餘設置,只要瀏覽器支持 CORS 跨域請求就行。 雖然實現 CORS 支持跨域的請求工做不須要作任務的事情,但有一些安全細節須要瞭解:

  • 若是 xhr.open() 方法傳入第四和第五個參數(用戶名和密碼)時,將不會經過跨域請求發送
  • 跨域請求默認狀況是不會攜帶 cookie 的。若是須要攜帶 cookie,那麼能夠在調用 send() 方法以前設置 XMLHttpRequestwithCredentials屬性爲 true
  • 監測瀏覽器是否支持 CORS 跨域請求,能夠直接經過檢測 XMLHttpRequestwithCredentials 的屬性是否存在便可。

注意:XMLHttpRequest 的跨域請求一樣包含簡單請求和非簡單請求,非簡單請求又會進行預檢請求,具體 CORS 的相關知識能夠查看以前的分享的文章點擊這裏

總結

XMLHttpRequest API很是的好用,並且目前市面上的主瀏覽器也基本上都支持。相比 fetch 而言,兼容性確定是更勝一籌,惟一不足的是不支持 Promise,可是這也難不倒咱們程序員,本身封裝一層就能夠了。更爲重要的是 XMLHttpRequest 支持超時設置和停止請求,還有進度事件,這些都是 'fetch' 所不具有的。

參考

相關文章
相關標籤/搜索