本文詳細講述如何使用原生 JS、jQuery 和 Fetch 來實現 AJAX。html
AJAX 即 Asynchronous JavaScript and XML,異步的 JavaScript 和 XML。使用 AJAX 能夠無刷新地向服務端發送請求接收服務端響應,並更新頁面。node
JS 實現 AJAX 主要基於瀏覽器提供的 XMLHttpRequest(XHR)類,全部現代瀏覽器(IE7+、Firefox、Chrome、Safari 以及 Opera)均內建 XMLHttpRequest 對象。jquery
// 獲取XMLHttpRequest對象 var xhr = new XMLHttpRequest();
若是須要兼容老版本的 IE (IE5, IE6) 瀏覽器,則能夠使用 ActiveX 對象:git
var xhr; if (window.XMLHttpRequest) { // Mozilla, Safari... xhr = new XMLHttpRequest(); } else if (window.ActiveXObject) { // IE try { xhr = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { xhr = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {} } }
接下來,咱們須要打開一個URL,而後發送這個請求。分別要用到 XMLHttpRequest 的 open() 方法和 send() 方法。es6
// GET var xhr; if (window.XMLHttpRequest) { // Mozilla, Safari... xhr = new XMLHttpRequest(); } else if (window.ActiveXObject) { // IE try { xhr = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { xhr = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {} } } if (xhr) { xhr.open('GET', '/api?username=admin&password=root', true); xhr.send(null); }
// POST var xhr; if (window.XMLHttpRequest) { // Mozilla, Safari... xhr = new XMLHttpRequest(); } else if (window.ActiveXObject) { // IE try { xhr = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { xhr = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {} } } if (xhr) { xhr.open('POST', '/api', true); // 設置 Content-Type 爲 application/x-www-form-urlencoded // 以表單的形式傳遞數據 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('username=admin&password=root'); }
open()
方法有三個參數:github
open()
的第一個參數是 HTTP 請求方式 – GET,POST,HEAD 或任何服務器所支持的您想調用的方式。按照HTTP規範,該參數要大寫;不然,某些瀏覽器(如Firefox)可能沒法處理請求。有關HTTP請求方法的詳細信息可參考 https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.htmlajax
第二個參數是請求頁面的 URL。因爲同源策略(Same origin policy)該頁面不能爲第三方域名的頁面。同時必定要保證在全部的頁面中都使用準確的域名,不然調用 open()
會獲得 permission denied
的錯誤提示。json
第三個參數設置請求是否爲異步模式。若是是 TRUE
,JavaScript 函數將繼續執行,而不等待服務器響應。這就是 AJAX 中的 A。api
若是第一個參數是 GET
,則能夠直接將參數放在 url 後面,如:http://nodejh.com/api?name=admint&password=root
。promise
若是第一個參數是 POST
,則須要將參數寫在 send() 方法裏面。send() 方法的參數能夠是任何想送給服務器的數據。這時數據要以字符串的形式送給服務器,如:name=admint&password=root
。或者也能夠傳遞 JSON 格式的數據:
// 設置 Content-Type 爲 application/json xhr.setRequestHeader('Content-Type', 'application/json'); // 傳遞 JSON 字符串 xhr.send(JSON.stringify({ username:'admin', password:'root' }));
若是不設置請求頭,原生 AJAX 會默認使用 Content-Type 是 text/plain;charset=UTF-8
的方式發送數據。
關於 Content-Type 更詳細的內容,將在之後的文章中解釋說明。
當發送請求時,咱們須要指定如何處理服務器的響應,咱們須要用到 onreadystatechange 屬性來檢測服務器的響應狀態。使用 onreadystatechange 有兩種方式,一是直接 onreadystatechange 屬性指定一個可調用的函數名,二是使用一個匿名函數:
// 方法一 指定可調用的函數 xhr.onreadystatechange = onReadyStateChange; function onReadyStateChange() { // do something } // 方法二 使用匿名函數 xhr.onreadystatechange = function(){ // do the thing };
接下來咱們須要在內部利用 readyState 屬性來獲取當前的狀態,當 readyState 的值爲 4,就意味着一個完整的服務器響應已經收到了,接下來就能夠處理該響應:
// readyState的取值以下 // 0 (未初始化) // 1 (正在裝載) // 2 (裝載完畢) // 3 (交互中) // 4 (完成) if (xhr.readyState === 4) { // everything is good, the response is received } else { // still not ready }
完整代碼以下:
// POST var xhr; if (window.XMLHttpRequest) { // Mozilla, Safari... xhr = new XMLHttpRequest(); } else if (window.ActiveXObject) { // IE try { xhr = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { xhr = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {} } } if (xhr) { xhr.onreadystatechange = onReadyStateChange; xhr.open('POST', '/api', true); // 設置 Content-Type 爲 application/x-www-form-urlencoded // 以表單的形式傳遞數據 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('username=admin&password=root'); } // onreadystatechange 方法 function onReadyStateChange() { // 該函數會被調用四次 console.log(xhr.readyState); if (xhr.readyState === 4) { // everything is good, the response is received if (xhr.status === 200) { console.log(xhr.responseText); } else { console.log('There was a problem with the request.'); } } else { // still not ready console.log('still not ready...'); } }
固然咱們能夠用onload來代替onreadystatechange等於4的狀況,由於onload只在狀態爲4的時候才被調用,代碼以下:
xhr.onload = function () { // 調用onload if (xhr.status === 200) { // status爲200表示請求成功 console.log('執行成功'); } else { console.log('執行出錯'); } }
然而須要注意的是,IE對 onload 屬性的支持並不友好。除了 onload 還有如下幾個屬性也能夠用來監測響應狀態:
onloadstart
onprogress
onabort
ontimeout
onerror
onloadend
jQuery 做爲一個使用人數最多的庫,其 AJAX 很好的封裝了原生 AJAX 的代碼,在兼容性和易用性方面都作了很大的提升,讓 AJAX 的調用變得很是簡單。下面即是一段簡單的 jQuery 的 AJAX 代碼:
$.ajax({ method: 'POST', url: '/api', data: { username: 'admin', password: 'root' } }) .done(function(msg) { alert( 'Data Saved: ' + msg ); });
對比原生 AJAX 的實現,使用 jQuery 就異常簡單了。固然咱們平時用的最多的,是下面兩種更簡單的方式:
// GET $.get('/api', function(res) { // do something }); // POST var data = { username: 'admin', password: 'root' }; $.post('/api', data, function(res) { // do something });
使用 jQuery 雖然能夠大大簡化 XMLHttpRequest 的使用,但 XMLHttpRequest 本質上但並非一個設計優良的 API:
不符合關注分離(Separation of Concerns)的原則
配置和調用方式很是混亂
使用事件機制來跟蹤狀態變化
基於事件的異步模型沒有現代的 Promise,generator/yield,async/await 友好
Fetch API 旨在修正上述缺陷,它提供了與 HTTP 語義相同的 JS 語法,簡單來講,它引入了 fetch()
這個實用的方法來獲取網絡資源。
Fetch 的瀏覽器兼容圖以下:
原生支持率並不高,幸運的是,引入下面這些 polyfill 後能夠完美支持 IE8+:
因爲 IE8 是 ES3,須要引入 ES5 的 polyfill: es5-shim, es5-sham
引入 Promise 的 polyfill: es6-promise
引入 fetch 探測庫:fetch-detector
引入 fetch 的 polyfill: fetch-ie8
可選:若是你還使用了 jsonp,引入 fetch-jsonp
可選:開啓 Babel 的 runtime 模式,如今就使用 async/await
先看一個簡單的 Fetch API 的例子 ? :
fetch('/api').then(function(response) { return response.json(); }).then(function(data) { console.log(data); }).catch(function(error) { console.log('Oops, error: ', error); });
使用 ES6 的箭頭函數後:
fetch('/api').then(response => response.json()) .then(data => console.log(data)) .catch(error => console.log('Oops, error: ', error))
能夠看出使用Fetch後咱們的代碼更加簡潔和語義化,鏈式調用的方式也使其更加流暢和清晰。但這種基於 Promise 的寫法仍是有 Callback 的影子,咱們還能夠用 async/await
來作最終優化:
async function() { try { let response = await fetch(url); let data = response.json(); console.log(data); } catch (error) { console.log('Oops, error: ', error); } }
使用 await
後,寫代碼就更跟同步代碼同樣。await
後面能夠跟 Promise 對象,表示等待 Promise resolve()
纔會繼續向下執行,若是 Promise 被 reject()
或拋出異常則會被外面的 try...catch
捕獲。
Promise,generator/yield,await/async 都是如今和將來 JS 解決異步的標準作法,能夠完美搭配使用。這也是使用標準 Promise 一大好處。
Fetch 請求默認是不帶 cookie,須要設置 fetch(url, {credentials: 'include'})
`
服務器返回 400,500 錯誤碼時並不會 reject,只有網絡錯誤這些致使請求不能完成時,fetch 纔會被 reject
接下來將上面基於 XMLHttpRequest 的 AJAX 用 Fetch 改寫:
var options = { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'admin', password: 'root' }), credentials: 'include' }; fetch('/api', options).then(response => response.json()) .then(data => console.log(data)) .catch(error => console.log('Oops, error: ', error))
Github Issue: https://github.com/nodejh/nodejh.github.io/issues/15