面試必備之ajax原始請求

XMLHttpRequest 對象

簡介

瀏覽器與服務器之間,采用 HTTP 協議通信。用戶在瀏覽器地址欄鍵入一個網址,或者通過網頁表單向服務器提交內容,這時瀏覽器就會向服務器發出 HTTP 請求。

1999年,微軟公司發佈 IE 瀏覽器5.0版,第一次引入新功能:允許 JavaScript 腳本向服務器發起 HTTP 請求。這個功能當時並沒有引起註意,直到2004年 Gmail 發佈和2005年 Google Map 發佈,才引起廣泛重視。2005年2月,AJAX 這個詞第一次正式提出,它是 Asynchronous JavaScript and XML 的縮寫,指的是通過 JavaScript 的異步通信,從服務器獲取 XML 文檔從中提取數據,再更新當前網頁的對應部分,而不用刷新整個網頁。後來,AJAX 這個詞就成為 JavaScript 腳本發起 HTTP 通信的代名詞,也就是說,隻要用腳本發起通信,就可以叫做 AJAX 通信。W3C 也在2006年發佈瞭它的國際標準。

具體來說,AJAX 包括以下幾個步驟。

  1. 創建 XMLHttpRequest 實例
  2. 發出 HTTP 請求
  3. 接收服務器傳回的數據
  4. 更新網頁數據

概括起來,就是一句話,AJAX 通過原生的XMLHttpRequest對象發出 HTTP 請求,得到服務器返回的數據後,再進行處理。現在,服務器返回的都是 JSON 格式的數據,XML 格式已經過時瞭,但是 AJAX 這個名字已經成瞭一個通用名詞,字面含義已經消失瞭。

XMLHttpRequest對象是 AJAX 的主要接口,用於瀏覽器與服務器之間的通信。盡管名字裡面有XML和Http,它實際上可以使用多種協議(比如file或ftp),發送任何格式的數據(包括字符串和二進制)。

XMLHttpRequest本身是一個構造函數,可以使用new命令生成實例。它沒有任何參數。

var xhr = new XMLHttpRequest();

一旦新建實例,就可以使用open()方法指定建立 HTTP 連接的一些細節。

xhr.open('GET', 'http://www.example.com/page.php', true);

上面代碼指定使用 GET 方法,跟指定的服務器網址建立連接。第三個參數true,表示請求是異步的。

然後,指定回調函數,監聽通信狀態(readyState屬性)的變化。

xhr.onreadystatechange = handleStateChange;

function handleStateChange() {
  // ...
}

上面代碼中,一旦XMLHttpRequest實例的狀態發生變化,就會調用監聽函數handleStateChange

最後使用send()方法,實際發出請求。

xhr.send(null);

上面代碼中,send()的參數為null,表示發送請求的時候,不帶有數據體。如果發送的是 POST 請求,這裡就需要指定數據體。

一旦拿到服務器返回的數據,AJAX 不會刷新整個網頁,而是隻更新網頁裡面的相關部分,從而不打斷用戶正在做的事情。

註意,AJAX 隻能向同源網址(協議、域名、端口都相同)發出 HTTP 請求,如果發出跨域請求,就會報錯(詳見《同源政策》和《CORS 通信》兩章)。

下面是XMLHttpRequest對象簡單用法的完整例子。

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function(){
  // 通信成功時,狀態值為4
  if (xhr.readyState === 4){
    if (xhr.status === 200){
      console.log(xhr.responseText);
    } else {
      console.error(xhr.statusText);
    }
  }
};

xhr.onerror = function (e) {
  console.error(xhr.statusText);
};

xhr.open('GET', '/endpoint', true);
xhr.send(null);

XMLHttpRequest 的實例屬性

XMLHttpRequest.readyState

XMLHttpRequest.readyState返回一個整數,表示實例對象的當前狀態。該屬性隻讀。它可能返回以下值。

0,表示 XMLHttpRequest 實例已經生成,但是實例的open()方法還沒有被調用。

1,表示open()方法已經調用,但是實例的send()方法還沒有調用,仍然可以使用實例的setRequestHeader()方法,設定 HTTP 請求的頭信息。

2,表示實例的send()方法已經調用,並且服務器返回的頭信息和狀態碼已經收到。

3,表示正在接收服務器傳來的數據體(body 部分)。這時,如果實例的responseType屬性等於text或者空字符串,responseText屬性就會包含已經收到的部分信息。

4,表示服務器返回的數據已經完全接收,或者本次接收已經失敗。

通信過程中,每當實例對象發生狀態變化,它的readyState屬性的值就會改變。這個值每一次變化,都會觸發readyStateChange事件。

var xhr = new XMLHttpRequest();

if (xhr.readyState === 4) {
  // 請求結束,處理服務器返回的數據
} else {
  // 顯示提示“加載中……”
}

上面代碼中,xhr.readyState等於4時,表明腳本發出的 HTTP 請求已經完成。其他情況,都表示 HTTP 請求還在進行中。

XMLHttpRequest.onreadystatechange

XMLHttpRequest.onreadystatechange屬性指向一個監聽函數。readystatechange事件發生時(實例的readyState屬性變化),就會執行這個屬性。

另外,如果使用實例的abort()方法,終止 XMLHttpRequest 請求,也會造成readyState屬性變化,導致調用XMLHttpRequest.onreadystatechange屬性。

下面是一個例子。

var xhr = new XMLHttpRequest();
xhr.open( 'GET', 'http://example.com' , true );
xhr.onreadystatechange = function () {
  if (xhr.readyState !== 4 || xhr.status !== 200) {
    return;
  }
  console.log(xhr.responseText);
};
xhr.send();

XMLHttpRequest.response

XMLHttpRequest.response屬性表示服務器返回的數據體(即 HTTP 回應的 body 部分)。它可能是任何數據類型,比如字符串、對象、二進制對象等等,具體的類型由XMLHttpRequest.responseType屬性決定。XMLHttpRequest.response屬性是隻讀的。

如果本次請求沒有成功或者數據不完整,該屬性等於null。但是,如果responseType屬性等於text或空字符串,在請求沒有結束之前(readyState等於3的階段),response屬性包含服務器已經返回的部分數據。

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function () {
  if (xhr.readyState === 4) {
    handler(xhr.response);
  }
}

XMLHttpRequest.responseType

XMLHttpRequest.responseType屬性是一個字符串,表示服務器返回數據的類型。這個屬性是可寫的,可以在調用open()方法之後、調用send()方法之前,設置這個屬性的值,告訴瀏覽器如何解讀返回的數據。如果responseType設為空字符串,就等同於默認值text。

XMLHttpRequest.responseType屬性可以等於以下值。

  • “”(空字符串):等同於text,表示服務器返回文本數據。
  • “arraybuffer”:ArrayBuffer 對象,表示服務器返回二進制數組。
  • “blob”:Blob 對象,表示服務器返回二進制對象。
  • “document”:Document 對象,表示服務器返回一個文檔對象。
  • “json”:JSON 對象。
  • “text”:字符串。

上面幾種類型之中,text類型適合大多數情況,而且直接處理文本也比較方便。document類型適合返回 HTML / XML 文檔的情況,這意味著,對於那些打開 CORS 的網站,可以直接用 Ajax 抓取網頁,然後不用解析 HTML 字符串,直接對抓取回來的數據進行 DOM 操作。blob類型適合讀取二進制數據,比如圖片文件。

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';

xhr.onload = function(e) {
  if (this.status === 200) {
    var blob = new Blob([xhr.response], {type: 'image/png'});
    // 或者
    var blob = xhr.response;
  }
};

xhr.send();

如果將這個屬性設為ArrayBuffer,就可以按照數組的方式處理二進制數據。

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'arraybuffer';

xhr.onload = function(e) {
  var uInt8Array = new Uint8Array(this.response);
  for (var i = 0, len = uInt8Array.length; i < len; ++i) {
    // var byte = uInt8Array[i];
  }
};

xhr.send();

如果將這個屬性設為json,瀏覽器就會自動對返回數據調用JSON.parse()方法。也就是說,從xhr.response屬性(註意,不是xhr.responseText屬性)得到的不是文本,而是一個 JSON 對象。

XMLHttpRequest.responseText

XMLHttpRequest.responseText屬性返回從服務器接收到的字符串,該屬性為隻讀。隻有 HTTP 請求完成接收以後,該屬性才會包含完整的數據。

var xhr = new XMLHttpRequest();
xhr.open('GET', '/server', true);

xhr.responseType = 'text';
xhr.onload = function () {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log(xhr.responseText);
  }
};

xhr.send(null);

XMLHttpRequest.responseXML

XMLHttpRequest.responseXML屬性返回從服務器接收到的 HTML 或 XML 文檔對象,該屬性為隻讀。如果本次請求沒有成功,或者收到的數據不能被解析為 XML 或 HTML,該屬性等於null。

該屬性生效的前提是 HTTP 回應的Content-Type頭信息等於text/xml或application/xml。這要求在發送請求前,XMLHttpRequest.responseType屬性要設為document。如果 HTTP 回應的Content-Type頭信息不等於text/xml和application/xml,但是想從responseXML拿到數據(即把數據按照 DOM 格式解析),那麼需要手動調用XMLHttpRequest.overrideMimeType()方法,強制進行 XML 解析。

該屬性得到的數據,是直接解析後的文檔 DOM 樹。

var xhr = new XMLHttpRequest();
xhr.open('GET', '/server', true);

xhr.responseType = 'document';
xhr.overrideMimeType('text/xml');

xhr.onload = function () {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log(xhr.responseXML);
  }
};

xhr.send(null);

XMLHttpRequest.responseURL

XMLHttpRequest.responseURL屬性是字符串,表示發送數據的服務器的網址。

var 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);

註意,這個屬性的值與open()方法指定的請求網址不一定相同。如果服務器端發生跳轉,這個屬性返回最後實際返回數據的網址。另外,如果原始 URL 包括錨點(fragment),該屬性會把錨點剝離。

XMLHttpRequest.status,XMLHttpRequest.statusText

XMLHttpRequest.status屬性返回一個整數,表示服務器回應的 HTTP 狀態碼。一般來說,如果通信成功的話,這個狀態碼是200;如果服務器沒有返回狀態碼,那麼這個屬性默認是200。請求發出之前,該屬性為0。該屬性隻讀。

  • 200, OK,訪問正常
  • 301, Moved Permanently,永久移動
  • 302, Moved temporarily,暫時移動
  • 304, Not Modified,未修改
  • 307, Temporary Redirect,暫時重定向
  • 401, Unauthorized,未授權
  • 403, Forbidden,禁止訪問
  • 404, Not Found,未發現指定網址
  • 500, Internal Server Error,服務器發生錯誤

基本上,隻有2xx和304的狀態碼,表示服務器返回是正常狀態。

if (xhr.readyState === 4) {
  if ( (xhr.status >= 200 && xhr.status < 300)
    || (xhr.status === 304) ) {
    // 處理服務器的返回數據
  } else {
    // 出錯
  }
}

XMLHttpRequest.statusText屬性返回一個字符串,表示服務器發送的狀態提示。不同於status屬性,該屬性包含整個狀態信息,比如“OK”和“Not Found”。在請求發送之前(即調用open()方法之前),該屬性的值是空字符串;如果服務器沒有返回狀態提示,該屬性的值默認為“OK”。該屬性為隻讀屬性。

XMLHttpRequest.timeout,XMLHttpRequestEventTarget.ontimeout

XMLHttpRequest.timeout屬性返回一個整數,表示多少毫秒後,如果請求仍然沒有得到結果,就會自動終止。如果該屬性等於0,就表示沒有時間限制。

XMLHttpRequestEventTarget.ontimeout屬性用於設置一個監聽函數,如果發生 timeout 事件,就會執行這個監聽函數。

下面是一個例子。

var xhr = new XMLHttpRequest();
var url = '/server';

xhr.ontimeout = function () {
  console.error('The request for ' + url + ' timed out.');
};

xhr.onload = function() {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      // 處理服務器返回的數據
    } else {
      console.error(xhr.statusText);
    }
  }
};

xhr.open('GET', url, true);
// 指定 10 秒鐘超時
xhr.timeout = 10 * 1000;
xhr.send(null);

事件監聽屬性

XMLHttpRequest 對象可以對以下事件指定監聽函數。

  • XMLHttpRequest.onloadstart:loadstart 事件(HTTP 請求發出)的監聽函數
  • XMLHttpRequest.onprogress:progress事件(正在發送和加載數據)的監聽函數
  • XMLHttpRequest.onabort:abort 事件(請求中止,比如用戶調用瞭abort()方法)的監聽函數
  • XMLHttpRequest.onerror:error 事件(請求失敗)的監聽函數
  • XMLHttpRequest.onload:load 事件(請求成功完成)的監聽函數
  • XMLHttpRequest.ontimeout:timeout 事件(用戶指定的時限超過瞭,請求還未完成)的監聽函數
  • XMLHttpRequest.onloadend:loadend 事件(請求完成,不管成功或失敗)的監聽函數

下面是一個例子。

xhr.onload = function() {
 var responseText = xhr.responseText;
 console.log(responseText);
 // process the response.
};

xhr.onabort = function () {
  console.log('The request was aborted');
};

xhr.onprogress = function (event) {
  console.log(event.loaded);
  console.log(event.total);
};

xhr.onerror = function() {
  console.log('There was an error!');
};

progress事件的監聽函數有一個事件對象參數,該對象有三個屬性:loaded屬性返回已經傳輸的數據量,total屬性返回總的數據量,lengthComputable屬性返回一個佈爾值,表示加載的進度是否可以計算。所有這些監聽函數裡面,隻有progress事件的監聽函數有參數,其他函數都沒有參數。

註意,如果發生網絡錯誤(比如服務器無法連通),onerror事件無法獲取報錯信息。也就是說,可能沒有錯誤對象,所以這樣隻能顯示報錯的提示。

XMLHttpRequest.withCredentials

XMLHttpRequest.withCredentials屬性是一個佈爾值,表示跨域請求時,用戶信息(比如 Cookie 和認證的 HTTP 頭信息)是否會包含在請求之中,默認為false,即向example.com發出跨域請求時,不會發送example.com設置在本機上的 Cookie(如果有的話)。

如果需要跨域 AJAX 請求發送 Cookie,需要withCredentials屬性設為true。註意,同源的請求不需要設置這個屬性。

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/', true);
xhr.withCredentials = true;
xhr.send(null);

為瞭讓這個屬性生效,服務器必須顯式返回Access-Control-Allow-Credentials這個頭信息。

Access-Control-Allow-Credentials: true

withCredentials屬性打開的話,跨域請求不僅會發送 Cookie,還會設置遠程主機指定的 Cookie。反之也成立,如果withCredentials屬性沒有打開,那麼跨域的 AJAX 請求即使明確要求瀏覽器設置 Cookie,瀏覽器也會忽略。

註意,腳本總是遵守同源政策,無法從document.cookie或者 HTTP 回應的頭信息之中,讀取跨域的 Cookie,withCredentials屬性不影響這一點。

XMLHttpRequest.upload

XMLHttpRequest 不僅可以發送請求,還可以發送文件,這就是 AJAX 文件上傳。發送文件以後,通過XMLHttpRequest.upload屬性可以得到一個對象,通過觀察這個對象,可以得知上傳的進展。主要方法就是監聽這個對象的各種事件:loadstart、loadend、load、abort、error、progress、timeout。

假定網頁上有一個<progress>元素。

<progress min="0" max="100" value="0">0% complete</progress>

文件上傳時,對upload屬性指定progress事件的監聽函數,即可獲得上傳的進度。

function upload(blobOrFile) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function (e) {};

  var progressBar = document.querySelector('progress');
  xhr.upload.onprogress = function (e) {
    if (e.lengthComputable) {
      progressBar.value = (e.loaded / e.total) * 100;
      // 兼容不支持 <progress> 元素的老式瀏覽器
      progressBar.textContent = progressBar.value;
    }
  };

  xhr.send(blobOrFile);
}

upload(new Blob(['hello world'], {type: 'text/plain'}));

XMLHttpRequest 的實例方法

XMLHttpRequest.open()

XMLHttpRequest.open()方法用於指定 HTTP 請求的參數,或者說初始化 XMLHttpRequest 實例對象。它一共可以接受五個參數。

void open(
   string method,
   string url,
   optional boolean async,
   optional string user,
   optional string password
);
  • method:表示 HTTP 動詞方法,比如GET、POST、PUT、DELETE、HEAD等。
  • url: 表示請求發送目標 URL。
  • async: 佈爾值,表示請求是否為異步,默認為true。如果設為false,則send()方法隻有等到收到服務器返回瞭結果,才會進行下一步操作。該參數可選。由於同步 AJAX 請求會造成瀏覽器失去響應,許多瀏覽器已經禁止在主線程使用,隻允許 Worker 裡面使用。所以,這個參數輕易不應該設為false。
  • user:表示用於認證的用戶名,默認為空字符串。該參數可選。
  • password:表示用於認證的密碼,默認為空字符串。該參數可選。

註意,如果對使用過open()方法的 AJAX 請求,再次使用這個方法,等同於調用abort(),即終止請求。

下面發送 POST 請求的例子。

var xhr = new XMLHttpRequest();
xhr.open('POST', encodeURI('someURL'));

XMLHttpRequest.send()

XMLHttpRequest.send()方法用於實際發出 HTTP 請求。它的參數是可選的,如果不帶參數,就表示 HTTP 請求隻有一個 URL,沒有數據體,典型例子就是 GET 請求;如果帶有參數,就表示除瞭頭信息,還帶有包含具體數據的信息體,典型例子就是 POST 請求。

下面是 GET 請求的例子。

var xhr = new XMLHttpRequest();
xhr.open('GET',
  'http://www.example.com/?id=' + encodeURIComponent(id),
  true
);
xhr.send(null);

上面代碼中,GET請求的參數,作為查詢字符串附加在 URL 後面。

下面是發送 POST 請求的例子。

var xhr = new XMLHttpRequest();
var data = 'email='
  + encodeURIComponent(email)
  + '&password='
  + encodeURIComponent(password);

xhr.open('POST', 'http://www.example.com', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(data);

註意,所有 XMLHttpRequest 的監聽事件,都必須在send()方法調用之前設定。

send方法的參數就是發送的數據。多種格式的數據,都可以作為它的參數。

void send();
void send(ArrayBufferView data);
void send(Blob data);
void send(Document data);
void send(String data);
void send(FormData data);

如果send()發送 DOM 對象,在發送之前,數據會先被串行化。如果發送二進制數據,最好是發送ArrayBufferView或Blob對象,這使得通過 Ajax 上傳文件成為可能。

下面是發送表單數據的例子。FormData對象可以用於構造表單數據。

var formData = new FormData();

formData.append('username', '張三');
formData.append('email', '[email protected]');
formData.append('birthDate', 1940);

var xhr = new XMLHttpRequest();
xhr.open('POST', '/register');
xhr.send(formData);

上面代碼中,FormData對象構造瞭表單數據,然後使用send()方法發送。它的效果與發送下面的表單數據是一樣的。

<form id='registration' name='registration' action='/register'>
  <input type='text' name='username' value='張三'>
  <input type='email' name='email' value='[email protected]'>
  <input type='number' name='birthDate' value='1940'>
  <input type='submit' onclick='return sendForm(this.form);'>
</form>

下面的例子是使用FormData對象加工表單數據,然後再發送。

function sendForm(form) {
  var formData = new FormData(form);
  formData.append('csrf', 'e69a18d7db1286040586e6da1950128c');

  var xhr = new XMLHttpRequest();
  xhr.open('POST', form.action, true);
  xhr.onload = function() {
    // ...
  };
  xhr.send(formData);

  return false;
}

var form = document.querySelector('#registration');
sendForm(form);

XMLHttpRequest.setRequestHeader()

XMLHttpRequest.setRequestHeader()方法用於設置瀏覽器發送的 HTTP 請求的頭信息。該方法必須在open()之後、send()之前調用。如果該方法多次調用,設定同一個字段,則每一次調用的值會被合並成一個單一的值發送。

該方法接受兩個參數。第一個參數是字符串,表示頭信息的字段名,第二個參數是字段值。

xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Content-Length', JSON.stringify(data).length);
xhr.send(JSON.stringify(data));

上面代碼首先設置頭信息Content-Type,表示發送 JSON 格式的數據;然後設置Content-Length,表示數據長度;最後發送 JSON 數據。

XMLHttpRequest.overrideMimeType()

XMLHttpRequest.overrideMimeType()方法用來指定 MIME 類型,覆蓋服務器返回的真正的 MIME 類型,從而讓瀏覽器進行不一樣的處理。舉例來說,服務器返回的數據類型是text/xml,由於種種原因瀏覽器解析不成功報錯,這時就拿不到數據瞭。為瞭拿到原始數據,我們可以把 MIME 類型改成text/plain,這樣瀏覽器就不會去自動解析,從而我們就可以拿到原始文本瞭。

xhr.overrideMimeType('text/plain')

註意,該方法必須在send()方法之前調用。

修改服務器返回的數據類型,不是正常情況下應該采取的方法。如果希望服務器返回指定的數據類型,可以用responseType屬性告訴服務器,就像下面的例子。隻有在服務器無法返回某種數據類型時,才使用overrideMimeType()方法。

var xhr = new XMLHttpRequest();
xhr.onload = function(e) {
  var arraybuffer = xhr.response;
  // ...
}
xhr.open('GET', url);
xhr.responseType = 'arraybuffer';
xhr.send();

XMLHttpRequest.getResponseHeader()

XMLHttpRequest.getResponseHeader()方法返回 HTTP 頭信息指定字段的值,如果還沒有收到服務器回應或者指定字段不存在,返回null。該方法的參數不區分大小寫。

function getHeaderTime() {
  console.log(this.getResponseHeader("Last-Modified"));
}

var xhr = new XMLHttpRequest();
xhr.open('HEAD', 'yourpage.html');
xhr.onload = getHeaderTime;
xhr.send();

如果有多個字段同名,它們的值會被連接為一個字符串,每個字段之間使用“逗號+空格”分隔。

XMLHttpRequest.getAllResponseHeaders()

XMLHttpRequest.getAllResponseHeaders()方法返回一個字符串,表示服務器發來的所有 HTTP 頭信息。格式為字符串,每個頭信息之間使用CRLF分隔(回車+換行),如果沒有收到服務器回應,該屬性為null。如果發生網絡錯誤,該屬性為空字符串。

var xhr = new XMLHttpRequest();
xhr.open('GET', 'foo.txt', true);
xhr.send();

xhr.onreadystatechange = function () {
  if (this.readyState === 4) {
    var headers = xhr.getAllResponseHeaders();
  }
}

上面代碼用於獲取服務器返回的所有頭信息。它可能是下面這樣的字符串。

date: Fri, 08 Dec 2017 21:04:30 GMT\r\n
content-encoding: gzip\r\n
x-content-type-options: nosniff\r\n
server: meinheld/0.6.1\r\n
x-frame-options: DENY\r\n
content-type: text/html; charset=utf-8\r\n
connection: keep-alive\r\n
strict-transport-security: max-age=63072000\r\n
vary: Cookie, Accept-Encoding\r\n
content-length: 6502\r\n
x-xss-protection: 1; mode=block\r\n

然後,對這個字符串進行處理。

var arr = headers.trim().split(/[\r\n]+/);
var headerMap = {};

arr.forEach(function (line) {
  var parts = line.split(': ');
  var header = parts.shift();
  var value = parts.join(': ');
  headerMap[header] = value;
});

headerMap['content-length'] // "6502"

XMLHttpRequest.abort()

XMLHttpRequest.abort()方法用來終止已經發出的 HTTP 請求。調用這個方法以後,readyState屬性變為4,status屬性變為0。

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://www.example.com/page.php', true);
setTimeout(function () {
  if (xhr) {
    xhr.abort();
    xhr = null;
  }
}, 5000);

上面代碼在發出5秒之後,終止一個 AJAX 請求。

XMLHttpRequest 實例的事件

readyStateChange 事件

readyState屬性的值發生改變,就會觸發 readyStateChange 事件。

我們可以通過onReadyStateChange屬性,指定這個事件的監聽函數,對不同狀態進行不同處理。尤其是當狀態變為4的時候,表示通信成功,這時回調函數就可以處理服務器傳送回來的數據。

progress 事件

上傳文件時,XMLHttpRequest 實例對象本身和實例的upload屬性,都有一個progress事件,會不斷返回上傳的進度。

var xhr = new XMLHttpRequest();

function updateProgress (oEvent) {
  if (oEvent.lengthComputable) {
    var percentComplete = oEvent.loaded / oEvent.total;
  } else {
    console.log('無法計算進展');
  }
}

xhr.addEventListener('progress', updateProgress);

xhr.open();

load 事件、error 事件、abort 事件

load 事件表示服務器傳來的數據接收完畢,error 事件表示請求出錯,abort 事件表示請求被中斷(比如用戶取消請求)。

var xhr = new XMLHttpRequest();

xhr.addEventListener('load', transferComplete);
xhr.addEventListener('error', transferFailed);
xhr.addEventListener('abort', transferCanceled);

xhr.open();

function transferComplete() {
  console.log('數據接收完畢');
}

function transferFailed() {
  console.log('數據接收出錯');
}

function transferCanceled() {
  console.log('用戶取消接收');
}

loadend 事件

abort、load和error這三個事件,會伴隨一個loadend事件,表示請求結束,但不知道其是否成功。

xhr.addEventListener('loadend', loadEnd);

function loadEnd(e) {
  console.log('請求結束,狀態未知');
}

timeout 事件

服務器超過指定時間還沒有返回結果,就會觸發 timeout 事件,具體的例子參見timeout屬性一節。

Navigator.sendBeacon()

用戶卸載網頁的時候,有時需要向服務器發一些數據。很自然的做法是在unload事件或beforeunload事件的監聽函數裡面,使用XMLHttpRequest對象發送數據。但是,這樣做不是很可靠,因為XMLHttpRequest對象是異步發送,很可能在它即將發送的時候,頁面已經卸載瞭,從而導致發送取消或者發送失敗。

解決方法就是unload事件裡面,加一些很耗時的同步操作。這樣就能留出足夠的時間,保證異步 AJAX 能夠發送成功。

function log() {
  let xhr = new XMLHttpRequest();
  xhr.open('post', '/log', true);
  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  xhr.send('foo=bar');
}

window.addEventListener('unload', function(event) {
  log();

  // a time-consuming operation
  for (let i = 1; i < 10000; i++) {
    for (let m = 1; m < 10000; m++) { continue; }
  }
});

上面代碼中,強制執行瞭一次雙重循環,拖長瞭unload事件的執行時間,導致異步 AJAX 能夠發送成功。

類似的還可以使用setTimeout。下面是追蹤用戶點擊的例子。

// HTML 代碼如下
// <a id="target" href="https://baidu.com">click</a>
const clickTime = 350;
const theLink = document.getElementById('target');

function log() {
  let xhr = new XMLHttpRequest();
  xhr.open('post', '/log', true);
  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  xhr.send('foo=bar');
}

theLink.addEventListener('click', function (event) {
  event.preventDefault();
  log();

  setTimeout(function () {
    window.location.href = theLink.getAttribute('href');
  }, clickTime);
});

上面代碼使用setTimeout,拖延瞭350毫秒,才讓頁面跳轉,因此使得異步 AJAX 有時間發出。

這些做法的共同問題是,卸載的時間被硬生生拖長瞭,後面頁面的加載被推遲瞭,用戶體驗不好。

為瞭解決這個問題,瀏覽器引入瞭Navigator.sendBeacon()方法。這個方法還是異步發出請求,但是請求與當前頁面線程脫鉤,作為瀏覽器進程的任務,因此可以保證會把數據發出去,不拖延卸載流程。

window.addEventListener('unload', logData, false);

function logData() {
  navigator.sendBeacon('/log', analyticsData);
}

Navigator.sendBeacon方法接受兩個參數,第一個參數是目標服務器的 URL,第二個參數是所要發送的數據(可選),可以是任意類型(字符串、表單對象、二進制對象等等)。

navigator.sendBeacon(url, data)

這個方法的返回值是一個佈爾值,成功發送數據為true,否則為false。

該方法發送數據的 HTTP 方法是 POST,可以跨域,類似於表單提交數據。它不能指定回調函數。

下面是一個例子。

// HTML 代碼如下
// <body onload="analytics('start')" onunload="analytics('end')">

function analytics(state) {
  if (!navigator.sendBeacon) return;

  var URL = 'http://example.com/analytics';
  var data = 'state=' + state + '&location=' + window.location;
  navigator.sendBeacon(URL, data);
}

總結

到此這篇關於面試必備之ajax原始請求的文章就介紹到這瞭,更多相關ajax原始請求內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: