詳解原生JavaScript實(shí)現(xiàn)jQuery中AJAX處理的方法
在這篇文章,我使用 Node.js作為后端。沒錯(cuò),這就可以全棧(瀏覽器和服務(wù)器)JS了。Node.js 是很簡(jiǎn)潔的,我鼓勵(lì)你能在 Github下載demo,并關(guān)注該項(xiàng)目。下面是服務(wù)器端的代碼:
// 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);
}
}
該代碼段通過檢測(cè)請(qǐng)求 URL,確定該app返回的相應(yīng)內(nèi)容。如果該請(qǐng)求來自 scripts 目錄,那么服務(wù)器將返回內(nèi)容類型(content type)為 application/javascript 的相應(yīng)文件。如果請(qǐng)求頭部的 x-requested-with 被設(shè)為 XMLHttpRequest,那么該請(qǐng)求是 Ajax 請(qǐng)求,然后返回相應(yīng)數(shù)據(jù)。除了以上兩種情況,服務(wù)器將會(huì)返回 views/index.html。
下面我將會(huì)展開上一代碼段處理 Ajax 請(qǐng)求的注釋部分進(jìn)行深入講解。在 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 函數(shù)異步讀取被請(qǐng)求文件的內(nèi)容。該函數(shù)向被作為回調(diào)函數(shù)的 httpHandler 傳遞一個(gè)引用。
httpHandler 函數(shù)檢測(cè) error 對(duì)象是否存在(如:被請(qǐng)求文件不能被打開,該對(duì)象就會(huì)存在)。另外,指定類型是好的做法,那么服務(wù)器返回的文件內(nèi)容就會(huì)擁有適當(dāng)?shù)?HTTP 狀態(tài)碼(status code)和內(nèi)容類型(content type)。
測(cè)試 API
讓我們?yōu)楹蠖薃PI編寫一些單元測(cè)試,從而確保它們能正確運(yùn)行。對(duì)于這類測(cè)試,我會(huì)請(qǐng)求 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);
});
這些測(cè)試確保了我們的 app 對(duì)于不同請(qǐng)求能返回正確的內(nèi)容類型(content type)和HTTP 狀態(tài)碼(status code)。一旦你安裝了這些依賴,那么你就能使用命令 npm test 運(yùn)行這些測(cè)試。
界面
現(xiàn)在,讓我們看看用戶界面的 HTML 代碼:
// views/index.html <h1>Vanilla Ajax without jQuery</h1> <button id="retrieve" data-url="/">Retrieve</button> <p id="results"></p>
上述的 HTML 代碼看起來很簡(jiǎn)潔。沒錯(cuò),正如你所看到的,所有令人興奮的事情都發(fā)生在 JavaScript。
onreadystate vs onload
如果你看過任何一本權(quán)威的、關(guān)于 Ajax 的書,你可能會(huì)發(fā)現(xiàn) onreadystate 在書上隨處可見。該回調(diào)函數(shù)需要通過嵌套的 ifs 或多個(gè) case 語句完成,這使得難以記憶。讓我們?cè)俅位仡?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();
});
}());
上述代碼會(huì)在 控制臺(tái)(console) 輸出以下語句:

onreadystatechange 事件能在請(qǐng)求的任何過程中被觸發(fā)。如能在每個(gè)請(qǐng)求前、請(qǐng)求末。但根據(jù)文檔,onload 事件只會(huì)在請(qǐng)求成功后觸發(fā)。又因?yàn)?onload 事件是一個(gè)常見的 API,所以你能在很短時(shí)間內(nèi)掌握它。onreadystatechange 事件可作為后備(原文是backwards compatible 向后兼容?)方案。而 onload 事件應(yīng)該是你的首選。而且 onload 事件與 jQuery 的 success 回調(diào)函數(shù)類似,難道不是嗎?
###設(shè)置請(qǐng)求頭部
jQuery 私下幫你設(shè)置請(qǐng)求頭部了,所以后端能檢測(cè)這是一個(gè) Ajax 請(qǐng)求。一般來說,后端并不關(guān)心 GET 請(qǐng)求是從哪而來,只要能返回正確的響應(yīng)即可。當(dāng)你相用同樣的 web API 返回 Ajax 或 HTML 時(shí),這就派上用場(chǎng)了。讓我們看看如何通過原生 JavaScript 設(shè)置請(qǐng)求頭部:
var oReq = new XMLHttpRequest();
oReq.open('GET', e.target.dataset.url, true);
oReq.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
oReq.send();
與此同時(shí),我們?cè)?Node.js 做一個(gè)檢測(cè):
if (req.headers['x-requested-with'] === 'XMLHttpRequest') {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({message: 'Hello World!'}));
}
正如你所看到的,原生 Ajax 是一個(gè)靈活且現(xiàn)代化的前端 API。你可以利用請(qǐng)求頭部做很多事情,其中一種是版本控制。例如,我想讓某個(gè) web API 支持多個(gè)版本。但我又不想利用 URL,取而代之的是:通過設(shè)置請(qǐng)求頭部,使客戶端能選擇它們想要的版本。所以,我們能這樣設(shè)置請(qǐng)求頭部:
oReq.setRequestHeader('x-vanillaAjaxWithoutjQuery-version', '1.0');
然后,在后端寫上相應(yīng)代碼:
if (req.headers['x-requested-with'] === 'XMLHttpRequest' &&
req.headers['x-vanillaajaxwithoutjquery-version'] === '1.0') {
// Send Ajax response
}
我們能利用 Node.js 為我們提供的 headers 對(duì)象進(jìn)行相應(yīng)檢測(cè)。而唯一需要注意的地方是:以小寫字母讀取它們。
響應(yīng)類型
你可能想知道為什么 responseText 返回的是字符串,而不是能被我們操作的普通 JSON(Plain Old JSON)。原來是因?yàn)槲覜]有設(shè)置合適的 responseType 屬性。該 Ajax 屬性會(huì)很好地告訴前端 API 所期望服務(wù)器返回的數(shù)據(jù)類型。所以,我們要好好利用它:
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();
哇,這樣我們就不必再對(duì)返回的純文本解析為 JSON 了,我們能告訴 API 我們期待接收的數(shù)據(jù)類型。該特性幾乎得到了所有最新主流瀏覽器的支持。當(dāng)然,jQuery 會(huì)自動(dòng)幫我們轉(zhuǎn)為適當(dāng)?shù)念愋?。但現(xiàn)在的原生 JavaScript 也具有方便的、完成同樣事件的方法。 原生 Ajax 已經(jīng)支持很多其它響應(yīng)類型,如 XML。
但遺憾的是,到 IE11 為止,開發(fā)團(tuán)隊(duì)仍未對(duì) xhr.responseType='json' 進(jìn)行支持。雖然該特性目前在 Microsoft Edge 得到支持。但這個(gè) Bug 提出幾乎兩年了。我堅(jiān)信 Microsoft 團(tuán)隊(duì)一直在努力地改進(jìn)瀏覽器。讓我們期待 Microsoft Edge、aka Project Spartan 當(dāng)初提出的承諾。
當(dāng)然,你可以這個(gè)解決這個(gè) 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;
}
};
避免緩存
對(duì) Ajax 請(qǐng)求進(jìn)行緩存的瀏覽器特性都快被我們忘記了。例如,IE 就默認(rèn)是這樣。我還曾因此導(dǎo)致我的 Ajax 不執(zhí)行而苦惱了幾個(gè)小時(shí)。幸運(yùn)的是,jQuery 默認(rèn)清除瀏覽器緩存。當(dāng)然,你能在純 Ajax 達(dá)到該目的,而且相當(dāng)簡(jiǎn)單:
var bustCache = '?' + new Date().getTime();
oReq.open('GET', e.target.dataset.url + bustCache, true);
查看 jQuery 文檔,可知道 jQuery 在每個(gè)請(qǐng)求(GET)后面追加一個(gè)時(shí)間戳作為查詢字符串。這在某個(gè)程度上讓請(qǐng)求變得獨(dú)一無二,并避免瀏覽器緩存。每當(dāng)你觸發(fā) HTTP Ajax 請(qǐng)求,你能看到類似如下請(qǐng)求:

OK!這就沒有戲劇性的事情發(fā)生了。
總結(jié)
我希望你能喜歡這篇關(guān)于原生 Ajax 的文章。Ajax 在過去某段時(shí)間里,被人們看作是一種可怕的怪獸。而事實(shí)上,我們已經(jīng)覆蓋了原生 Ajax 所有基礎(chǔ)知識(shí)。
最后,我會(huì)留給你一個(gè)簡(jiǎn)潔的方式進(jìn)行Ajax調(diào)用:
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();
- javascript實(shí)現(xiàn)原生ajax的幾種方法介紹
- 原生Javascript封裝的一個(gè)AJAX函數(shù)分享
- 原生 JS Ajax,GET和POST 請(qǐng)求實(shí)例代碼
- JavaScript原生xmlHttp與jquery的ajax方法json數(shù)據(jù)格式實(shí)例
- javascript原生ajax寫法分享
- 原生JavaScript實(shí)現(xiàn)Ajax的方法
- 實(shí)例講解使用原生JavaScript處理AJAX請(qǐng)求的方法
- 原生JS封裝ajax 傳json,str,excel文件上傳提交表單(推薦)
- 原生js仿jquery實(shí)現(xiàn)對(duì)Ajax的封裝
相關(guān)文章
Echarts圖表如何利用formatter自定義tooltip的內(nèi)容和樣式
這篇文章主要給大家介紹了關(guān)于Echarts圖表如何利用formatter自定義tooltip的內(nèi)容和樣式的相關(guān)資料,echarts的圖表配置非常的靈活自由,但是不熟悉的時(shí)候容易不知道怎么配置,需要的朋友可以參考下2023-06-06
JavaScript設(shè)計(jì)模式之抽象工廠模式介紹
這篇文章主要介紹了JavaScript設(shè)計(jì)模式之抽象工廠模式介紹,抽象工廠模式就是對(duì)功能類單獨(dú)創(chuàng)建工廠類,這樣就不必修改之前的代碼,又?jǐn)U展了功能,需要的朋友可以參考下2014-12-12
javascript 刪除數(shù)組中重復(fù)項(xiàng)(uniq)
巧妙去除數(shù)組中的重復(fù)項(xiàng)的方法參考,需要的朋友可以參考下。2010-01-01
js實(shí)現(xiàn)遍歷含有input的table實(shí)例
這篇文章主要介紹了js實(shí)現(xiàn)遍歷含有input的table方法,結(jié)合實(shí)例形式分析了jsp讀取數(shù)據(jù)庫動(dòng)態(tài)生成table及JavaScript遍歷table的相關(guān)技巧,需要的朋友可以參考下2015-12-12
用JavaScript玩轉(zhuǎn)游戲物理(一)運(yùn)動(dòng)學(xué)模擬與粒子系統(tǒng)
也許,三百年前的艾薩克·牛頓爵士(Sir Issac Newton, 1643-1727)并沒幻想過,物理學(xué)廣泛地應(yīng)用在今天許多游戲、動(dòng)畫中。2010-06-06

