英文出處:《A Guide to Vanilla Ajax Without jQuery》javascript
翻譯: 劉健超 J.cphp
注意:未經許可,禁止轉載!html
Ajax 是異步的JavaScript和XML的簡稱,是一種更新頁面某部分的機制。它賦予了你從服務器獲取數據後,更新頁面某部分的權力,從而避免了刷新整個頁面。另外,以此方式實現頁面局部更新,不只能有效地打造流暢的用戶體驗,並且減輕了服務器的負載。前端
下面是對一個基本的 Ajax 請求進行剖析:java
var xhr = new XMLHttpRequest(); xhr.open('GET', 'send-ajax-data.php'); xhr.send(null);
在這裏, 咱們建立了一個能向服務器發出 HTTP 請求的類的實例。而後調用其 open
方法,其中第一個參數是 HTTP 請求方法,第二個參數是請求頁面的 URL。最後,咱們調用參數爲 null 的 send
方法。假如使用 POST 請求方法(這裏咱們使用了 GET),那麼 send 方法 的參數應該包含任何你想發送的數據。node
下面是咱們如何處理服務器的響應:jquery
xhr.onreadystatechange = function(){ var DONE = 4; // readyState 4 表明已向服務器發送請求 var OK = 200; // status 200 表明服務器返回成功 if(xhr.readyState === DONE){ if(xhr.status === OK){ console.log(xhr.responseText); // 這是返回的文本 } else{ console.log("Error: "+ xhr.status); // 在這次請求中發生的錯誤 } } }
onreadystatechange
是異步的,那麼這就意味着它將可在任什麼時候候被調用。這種類型的函數被稱爲回調函數——一旦某些處理完成後,它就會被調用。在此案例中,這個處理髮生在服務器。git
對於想學習更多關於 Ajax 基礎知識的同窗,可關注 MDN 的這篇教程。github
嗯,好消息是上述代碼兼容全部最新的主流瀏覽器。而壞消息是使用起來十分複雜。的確使人噁心!我已經苦思出一個優雅的解決方案了。web
若是使用 jQuery,則能把上述代碼壓縮成這樣:
$.ajax({ url: "send-ajax-data.php" }).done(function(res){ console.log(res); }).fail(function(){ console.log("Error: " + err.status); })
很是簡潔易懂。對於大多數人(我想也包括你)來講,jQuery 已經成爲了解決 Ajax 的默認標準。但你知道嗎?狀況不必定是這樣的。jQuery 的存在是爲了解決醜陋的 DOM API。但 Ajax 真的是醜陋或複雜的嗎?
在文章的剩餘部分,我打算用原生 JavaScript 使 Ajax API 有所改善。關於 Ajax 的完整文檔能夠在 W3C 找到。然而這份說明的標題使我很是受打擊。居然不是「XMLHttpRequest Level 2」,而是「XMLHttpRequest Level 1」——由於在2011年已將兩個文檔合併。展望將來,它們將被視爲單一實體,而現存標準將其稱爲 XMLHttpRequest。這代表社區堅持承諾只有一個標準,這對於想脫離 jQuery 的開發人員來講,是個好消息。
因此,讓咱們一塊兒開始吧!
在這篇文章,我使用 Node.js做爲後端。沒錯,這就能夠全棧(瀏覽器和服務器)JS了。Node.js 是很簡潔的,我鼓勵你能在 Github下載demo,並關注該項目。下面是服務器端的代碼:
// app.js var app = http.createServer(function(req, res){ if(req.url.indexOf("/scripts/") >= 0){ render(req.url.slice(1), "application/javascript", httpHandler); } else if(req.headers['x-requested-with'] === 'XMLHttpRequest'){ // Send Ajax response } else{ render('views/index.html', 'text/html', httpHandler); } }
該代碼段經過檢測請求 URL,肯定該app返回的相應內容。若是該請求來自 scripts
目錄,那麼服務器將返回內容類型(content type)爲 application/javascript
的相應文件。若是請求頭部的 x-requested-with
被設爲 XMLHttpRequest
,那麼該請求是 Ajax 請求,而後返回相應數據。除了以上兩種狀況,服務器將會返回 views/index.html
。
下面我將會展開上一代碼段處理 Ajax 請求的註釋部分進行深刻講解。在 Node.js端,我已處理了 render
和 httpHandler
的體力活:
// app.js function render(path, contentType, fn) { fs.readFile(__dirname + '/' + path, 'utf-8', function (err, str) { fn(err, str, contentType); }); } var httpHandler = function (err, str, contentType) { if (err) { res.writeHead(500, {'Content-Type': 'text/plain'}); res.end('An error has occured: ' + err.message); } else { res.writeHead(200, {'Content-Type': contentType}); res.end(str); } };
render
函數異步讀取被請求文件的內容。該函數向被做爲回調函數的 httpHandler
傳遞一個引用。httpHandler
函數檢測 error 對象是否存在(如:被請求文件不能被打開,該對象就會存在)。另外,指定類型是好的作法,那麼服務器返回的文件內容就會擁有適當的 HTTP 狀態碼(status code)和內容類型(content type)。
讓咱們爲後端API編寫一些單元測試,從而確保它們能正確運行。對於這類測試,我會請求 supertest 和 mocha幫助。
// test/app.request.js it("responds with html", function(done){ request(app) .get("/") .expect("Content-Type", /html/) .expect(200, done); }); it('responds with javascript', function (done) { request(app) .get('/scripts/index.js') .expect('Content-Type', /javascript/) .expect(200, done); }); it('responds with json', function (done) { request(app) .get('/') .set('X-Requested-With', 'XMLHttpRequest') .expect('Content-Type', /json/) .expect(200, done); });
這些測試確保了咱們的 app 對於不一樣請求能返回正確的內容類型(content type)和HTTP 狀態碼(status code)。一旦你安裝了這些依賴,那麼你就能使用命令 npm test
運行這些測試。
如今,讓咱們看看用戶界面的 HTML 代碼:
// views/index.html <h1>Vanilla Ajax without jQuery</h1> <button id="retrieve" data-url="/">Retrieve</button> <p id="results"></p>
上述的 HTML 代碼看起來很簡潔。沒錯,正如你所看到的,全部使人興奮的事情都發生在 JavaScript。
若是你看過任何一本權威的、關於 Ajax 的書,你可能會發現 onreadystate
在書上隨處可見。該回調函數須要經過嵌套的 ifs 或多個 case 語句完成,這使得難以記憶。讓咱們再次回顧 onreadystate
和 onload
事件。
(function() { var retrieve = document.getElementById('retrieve'), results = document.getElementById('results'), toReadyStateDescription = function(state) { switch (state) { case 0: return 'UNSENT'; case 1: return 'OPENED'; case 2: return 'HEADERS_RECEIVED'; case 3: return 'LOADING'; case 4: return 'DONE'; default: return ''; } }; retrieve.addEventListener('click', function(e) { var oReq = new XMLHttpRequest(); oReq.onload = function() { console.log('Inside the onload event'); }; oReq.onreadystatechange = function() { console.log('Inside the onreadystatechange ev![此處輸入圖片的描述][1]ent with readyState: ' + toReadyStateDescription(oReq.readyState)); }; oReq.open('GET', e.target.dataset.url, true); oReq.send(); }); }());
上述代碼會在 控制檯(console) 輸出如下語句:
onreadystatechange
事件能在請求的任何過程當中被觸發。如能在每一個請求前、請求末。但根據文檔,onload
事件只會在請求成功後觸發。又由於 onload
事件是一個常見的 API,因此你能在很短期內掌握它。onreadystatechange
事件可做爲後備(原文是backwards compatible 向後兼容?)方案。而 onload
事件應該是你的首選。並且 onload
事件與 jQuery 的 success
回調函數相似,難道不是嗎?
###設置請求頭部
jQuery 私下幫你設置請求頭部了,因此後端能檢測這是一個 Ajax 請求。通常來講,後端並不關心 GET 請求是從哪而來,只要能返回正確的響應便可。當你相用一樣的 web API 返回 Ajax 或 HTML 時,這就派上用場了。讓咱們看看如何經過原生 JavaScript 設置請求頭部:
var oReq = new XMLHttpRequest(); oReq.open('GET', e.target.dataset.url, true); oReq.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); oReq.send();
與此同時,咱們在 Node.js 作一個檢測:
if (req.headers['x-requested-with'] === 'XMLHttpRequest') { res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify({message: 'Hello World!'})); }
正如你所看到的,原生 Ajax 是一個靈活且現代化的前端 API。你能夠利用請求頭部作不少事情,其中一種是版本控制。例如,我想讓某個 web API 支持多個版本。但我又不想利用 URL,取而代之的是:經過設置請求頭部,使客戶端能選擇它們想要的版本。因此,咱們能這樣設置請求頭部:
oReq.setRequestHeader('x-vanillaAjaxWithoutjQuery-version', '1.0');
而後,在後端寫上相應代碼:
if (req.headers['x-requested-with'] === 'XMLHttpRequest' && req.headers['x-vanillaajaxwithoutjquery-version'] === '1.0') { // Send Ajax response }
咱們能利用 Node.js 爲咱們提供的 headers 對象進行相應檢測。而惟一須要注意的地方是:以小寫字母讀取它們。
你可能想知道爲何 responseText
返回的是字符串,而不是能被咱們操做的普通 JSON(Plain Old JSON)。原來是由於我沒有設置合適的 responseType
屬性。該 Ajax 屬性會很好地告訴前端 API 所指望服務器返回的數據類型。因此,咱們要好好利用它:
var oReq = new XMLHttpRequest(); oReq.onload = function (e) { results.innerHTML = e.target.response.message; }; oReq.open('GET', e.target.dataset.url, true); oReq.responseType = 'json'; oReq.send();
哇,這樣咱們就沒必要再對返回的純文本解析爲 JSON 了,咱們能告訴 API 咱們期待接收的數據類型。該特性幾乎獲得了全部最新主流瀏覽器的支持。固然,jQuery 會自動幫咱們轉爲適當的類型。但如今的原生 JavaScript 也具備方便的、完成一樣事件的方法。 原生 Ajax 已經支持不少其它響應類型,如 XML。
但遺憾的是,到 IE11 爲止,開發團隊仍未對 xhr.responseType='json' 進行支持。雖然該特性目前在 Microsoft Edge 獲得支持。但這個 Bug 提出幾乎兩年了。我堅信 Microsoft 團隊一直在努力地改進瀏覽器。讓咱們期待 Microsoft Edge、aka Project Spartan 當初提出的承諾。
固然,你能夠這個解決這個 IE 問題:
oReq.onload = function (e) { var xhr = e.target; if (xhr.responseType === 'json') { results.innerHTML = xhr.response.message; } else { results.innerHTML = JSON.parse(xhr.responseText).message; } };
對 Ajax 請求進行緩存的瀏覽器特性都快被咱們忘記了。例如,IE 就默認是這樣。我還曾所以致使個人 Ajax 不執行而苦惱了幾個小時。幸運的是,jQuery 默認清除瀏覽器緩存。固然,你能在純 Ajax 達到該目的,並且至關簡單:
var bustCache = '?' + new Date().getTime(); oReq.open('GET', e.target.dataset.url + bustCache, true);
查看 jQuery 文檔,可知道 jQuery 在每一個請求(GET)後面追加一個時間戳做爲查詢字符串。這在某個程度上讓請求變得獨一無二,並避免瀏覽器緩存。每當你觸發 HTTP Ajax 請求,你能看到相似以下請求:
OK!這就沒有戲劇性的事情發生了。
我但願你能喜歡這篇關於原生 Ajax 的文章。Ajax 在過去某段時間裏,被人們看做是一種可怕的怪獸。而事實上,咱們已經覆蓋了原生 Ajax 全部基礎知識。
最後,我會留給你一個簡潔的方式進行Ajax調用:
var oReq = new XMLHttpRequest(); oReq.onload = function (e) { results.innerHTML = e.target.response.message; }; oReq.open('GET', e.target.dataset.url + '?' + new Date().getTime(), true); oReq.responseType = 'json'; oReq.send();
不要忘記,你能在 Github 找到整個案例。我但願在評論裏看到你對原生 Ajax 的想法。
感謝您的閱讀。
若是你以爲這篇文章對您有幫助或者以爲我翻譯得不錯,那給我個 star 吧.