[譯]脫離jQuery,使用原生Ajax

脫離jQuery,使用原生Ajax


英文出處:《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

到底選擇 jQuery 仍是選擇原生 JavaScript 呢?

嗯,好消息是上述代碼兼容全部最新的主流瀏覽器。而壞消息是使用起來十分複雜。的確使人噁心!我已經苦思出一個優雅的解決方案了。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端,我已處理了 renderhttpHandler 的體力活:

// 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

讓咱們爲後端API編寫一些單元測試,從而確保它們能正確運行。對於這類測試,我會請求 supertestmocha幫助。

// 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。

onreadystate vs onload

若是你看過任何一本權威的、關於 Ajax 的書,你可能會發現 onreadystate 在書上隨處可見。該回調函數須要經過嵌套的 ifs 或多個 case 語句完成,這使得難以記憶。讓咱們再次回顧 onreadystateonload 事件。

(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 吧.

相關文章
相關標籤/搜索