面試必備之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 包括以下幾個步驟。
- 創建 XMLHttpRequest 實例
- 發出 HTTP 請求
- 接收服務器傳回的數據
- 更新網頁數據
概括起來,就是一句話,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!