瀏覽器與服務(wù)器之間,采用HTTP協(xié)議通信。用戶在瀏覽器地址欄鍵入一個(gè)網(wǎng)址,或者通過網(wǎng)頁表單向服務(wù)器提交內(nèi)容,這時(shí)瀏覽器就會(huì)向服務(wù)器發(fā)出HTTP請求。
1999年,微軟公司發(fā)布IE瀏覽器5.0版,第一次引入新功能:允許JavaScript腳本向服務(wù)器發(fā)起HTTP請求。這個(gè)功能當(dāng)時(shí)并沒有引起注意,直到2004年Gmail發(fā)布和2005年Google Map發(fā)布,才引起廣泛重視。2005年2月,AJAX這個(gè)詞第一次正式提出,指圍繞這個(gè)功能進(jìn)行開發(fā)的一整套做法。從此,AJAX成為腳本發(fā)起HTTP通信的代名詞,W3C也在2006年發(fā)布了它的國際標(biāo)準(zhǔn)。
具體來說,AJAX包括以下幾個(gè)步驟。
- 創(chuàng)建AJAX對象
- 發(fā)出HTTP請求
- 接收服務(wù)器傳回的數(shù)據(jù)
- 更新網(wǎng)頁數(shù)據(jù)
概括起來,就是一句話,AJAX通過原生的XMLHttpRequest對象發(fā)出HTTP請求,得到服務(wù)器返回的數(shù)據(jù)后,再進(jìn)行處理。
AJAX可以是同步請求,也可以是異步請求。但是,大多數(shù)情況下,特指異步請求。因?yàn)橥降腁jax請求,對瀏覽器有“堵塞效應(yīng)”。
注意,AJAX只能向同源網(wǎng)址(協(xié)議、域名、端口都相同)發(fā)出HTTP請求,如果發(fā)出跨源請求,就會(huì)報(bào)錯(cuò)。
1、AJAX 是什么?有什么作用?
AJAX:是對Asynchronous JavaScript and XML的簡寫,是一種在無需重新加載整個(gè)網(wǎng)頁的情況下,能夠更新部分網(wǎng)頁的技術(shù)。這一技術(shù)能夠向服務(wù)器請求額外的數(shù)據(jù)而無需從新加載頁面。
作用:傳統(tǒng)的網(wǎng)頁(不使用 AJAX)如果需要更新內(nèi)容,必需重載整個(gè)網(wǎng)頁面。而通過使用ajax可以在后臺(tái)與服務(wù)器進(jìn)行少量數(shù)據(jù)交換, 可以使網(wǎng)頁實(shí)現(xiàn)異步更新。這意味著可以在不重新加載整個(gè)網(wǎng)頁的情況下,對網(wǎng)頁的某部分進(jìn)行更新。
2、Ajax和XMLHttpRequest
Ajax核心的技術(shù)是XMLHttpRequest對象(簡稱XHR)。我們通常將Ajax等同于XMLHttpRequest,但細(xì)究起來它們兩個(gè)是屬于不同維度的2個(gè)概念。
Ajax:(摘自what is Ajax)AJAX stands for Asynchronous JavaScript and XML. AJAX is a new technique for creating better, faster, and more interactive web applications with the help of XML, HTML, CSS, and Java Script.
AJAX is based on the following open standards:
- Browser-based presentation using HTML and Cascading Style Sheets (CSS).
- Data is stored in XML format and fetched from the server.
- Behind-the-scenes data fetches using XMLHttpRequest objects in the browser.
- JavaScript to make everything happen.
從上面的解釋中可以知道:Ajax是一種技術(shù)方案,但并不是一種新技術(shù)。它依賴的是現(xiàn)有的CSS/HTML/Javascript,而其中最核心的依賴是瀏覽器提供的 XMLHttpRequest對象,是這個(gè)對象使得瀏覽器可以發(fā)出HTTP請求與接收HTTP響應(yīng)。
所以用一句話來總結(jié)兩者的關(guān)系,就是:我們使用XMLHttpRequest對象來發(fā)送一個(gè)Ajax請求。
3、XMLHttpRequest對象
1、什么是XMLHttpRequest?
-
XMLHttpRequest是原生JS的一個(gè)內(nèi)置對象,用來在瀏覽器與服務(wù)器之間傳送數(shù)據(jù),一旦拿到服務(wù)器返回的數(shù)據(jù),AJAX不會(huì)刷新整個(gè)網(wǎng)頁,而是只更新相關(guān)部分,從而不打斷用戶正在做的事情。XMLHttpRequest是AJAX技術(shù)的核心,學(xué)習(xí)AJAX實(shí)質(zhì)上就是在學(xué)習(xí)XMLHttpRequest。
2、如何創(chuàng)建XMLHttpRequest對象:
- 一般使用new關(guān)鍵字進(jìn)行創(chuàng)建,然后賦值給一個(gè)變量,如下:
var xhr = new XMLHttpRequest();
4、XMLHttpRequest對象的常用屬性
1、readyState
只讀屬性,表示XMLHttpRequest請求當(dāng)前所處的狀態(tài),共有五個(gè)數(shù)字值(0,1,2,3,4,5)。
- 0:表示
XMLHttpRequest實(shí)例已經(jīng)生成,但是open()方法還沒有被調(diào)用。 - 1:表示已調(diào)用
open方法,但還未調(diào)用send方法(請求還未被發(fā)送出去),仍然可以使用setRequestHeader(),設(shè)定HTTP請求的頭信息。 - 2:表示
send方法已調(diào)用,數(shù)據(jù)已發(fā)送,并且服務(wù)器接收到了請求。 - 3:表示服務(wù)器正在傳輸數(shù)據(jù)。
- 4:表示數(shù)據(jù)傳輸完成。
在通信過程中,每當(dāng)發(fā)生狀態(tài)變化的時(shí)候,readyState屬性的值就會(huì)發(fā)生改變。這個(gè)值每一次變化,都會(huì)觸發(fā)readyStateChange事件。
2、status
只讀屬性,表示本次請求所得到的HTTP狀態(tài)碼,返回一個(gè)整數(shù)。一般來說,如果通信成功的話,這個(gè)狀態(tài)碼是200。常用的有如下幾個(gè)狀態(tài)碼:
- 200:OK(正常訪問);
- 301:Moved Permanently(永久移動(dòng));
- 302:Moved temporarily(暫時(shí)移動(dòng));
- 304:Not Modified(未修改);
- 307:Temporary Redirect(暫時(shí)重定向);
- 401:Unauthorized (未授權(quán));
- 403:Forbidden(禁止訪問);
- 404:Not Found(未找到該網(wǎng)址);
- 500:Internal Server Error (找到網(wǎng)址但服務(wù)器發(fā)生錯(cuò)誤);
基本上,只有200和304的狀態(tài)碼,表示服務(wù)器返回是正常狀態(tài)。|
3、 statusText
與status屬性類似,返回本次請求的狀態(tài),不同點(diǎn)在于,status只返回一個(gè)數(shù)字,而該屬性返回一個(gè)字符串 ,包含整個(gè)狀態(tài)信息,比如”200 OK“|
4、responseType
responseType屬性用來指定服務(wù)器返回?cái)?shù)據(jù)(xhr.response)的類型??赏ㄟ^對該屬性賦值來指定接收的數(shù)據(jù)類型,默認(rèn)為字符串,有如下幾種數(shù)據(jù)類型:
-
text:以字符串形式接收數(shù)據(jù); -
json:以json對象形式接收數(shù)據(jù); -
blob:blob對象; -
ArrayBuffer:ArrayBuffer對象;
5、response、responseText、responseXML
三者都是服務(wù)器返回的數(shù)據(jù),如果數(shù)據(jù)不完整或者獲取失敗,它們的值就為null。
不同點(diǎn):
response返回的是數(shù)據(jù)的主體部分,可以為任何類型(數(shù)組,json,XML,字符串等);
responseText返回從服務(wù)器接收到的字符串。該屬性為只讀。如果本次請求沒有成功或者數(shù)據(jù)不完整,該屬性就會(huì)等于null。如果服務(wù)器返回的數(shù)據(jù)格式是JSON,就可以使用responseText屬性;
//返回JSON格式的字符串
var data = ajax.responseText;
//把JSON格式的字符串轉(zhuǎn)換為JavaScript對象
data = JSON.parse(data);
- responseXML返回從服務(wù)器接收到的Document對象,該屬性為只讀。如果本次請求沒有成功,或者數(shù)據(jù)不完整,或者不能被解析為XML或HTML,該屬性等于null。該值返回的數(shù)據(jù)會(huì)被直接解析DOM;
5、XMLHttpRequest對象的常用方法
1、abort()
abort方法用來終止已經(jīng)發(fā)出的HTTP請求。
2、getAllResponseHeaders()
getAllResponseHeaders方法返回服務(wù)器發(fā)來的所有HTTP頭信息。格式為字符串,每個(gè)頭信息之間使用CRLF分隔,如果沒有受到服務(wù)器回應(yīng),該屬性返回null,該方法不需要接受參數(shù)。
3、getResponseHeader()
getResponseHeader方法返回HTTP頭信息指定字段的值,如果還沒有收到服務(wù)器回應(yīng)或者指定字段不存在,則該屬性為null。該方法需要接受一個(gè)參數(shù),用來返回指定字段的值。
4、open()
XMLHttpRequest對象的open方法用于指定發(fā)送HTTP請求的參數(shù),常用的有三個(gè)參數(shù):
第一個(gè)參數(shù):請求的類型(常用get或者post);
第二個(gè)參數(shù)是接口名和:這里要分兩種情況:
get請求時(shí):接口名+請求參數(shù)(鍵值對形式);post請求時(shí):只需要接口名(需要傳遞的參數(shù)寫在
send方法里);第三個(gè)參數(shù):一個(gè)布爾值,指定是否異步(true為異步,false為同步,通常為true,默認(rèn)為true);
第四和第五個(gè)參數(shù):填寫用于認(rèn)證的用戶名和密碼;
5、send()
send方法用于實(shí)際發(fā)出HTTP請求。如果不帶參數(shù),就表示HTTP請求只包含頭信息,也就是只有一個(gè)URL,典型例子就是GET請求;如果帶有參數(shù),就表示除了頭信息,還帶有包含具體數(shù)據(jù)的信息體,典型例子就是POST請求。
如果是POST請求還要在open()之后、send()之前使setRequestHeader方法設(shè)置HTTP頭信息。
ajax.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
6、setRequestHeader()
setRequestHeader方法用于設(shè)置HTTP頭信息。該方法必須在open()之后、send()之前調(diào)用。
6、XMLHttpRequest對象的事件以及對應(yīng)的事件監(jiān)聽接口

7、 前后端開發(fā)聯(lián)調(diào)需要注意哪些事情?后端接口完成前如何 mock 數(shù)據(jù)?
mock數(shù)據(jù)指的是在后端開發(fā)沒有完成時(shí),前端可以通過mock方法搭建本地服務(wù)器,模擬后臺(tái)數(shù)據(jù)來實(shí)現(xiàn)數(shù)據(jù)交互的效果
前后端開發(fā)聯(lián)調(diào)需要注意哪些事情:
約定數(shù)據(jù):有哪些需要傳輸?shù)臄?shù)據(jù),數(shù)據(jù)類型是什么。
約定接口:確定接口名稱以及請求和響應(yīng)的方法(get or post),請求的參數(shù)名稱,響應(yīng)的數(shù)據(jù)格式。
根據(jù)這些約定整理成接口文檔。
后端接口完成前如何 mock 數(shù)據(jù):
根據(jù)接口文檔,使用假數(shù)據(jù)來驗(yàn)證制作的網(wǎng)頁響應(yīng)和接口是否正常。
可以使用server-mock。
3,可以搭建php本地服務(wù)器用,php寫腳本提供臨時(shí)數(shù)據(jù)。
8、點(diǎn)擊按鈕,使用 ajax 獲取數(shù)據(jù),如何在數(shù)據(jù)到來之前防止重復(fù)點(diǎn)擊?
利用布爾值設(shè)置一個(gè)狀態(tài)鎖,在觸發(fā)ajax前和數(shù)據(jù)到來的時(shí)候布爾值設(shè)置為true,是不鎖定的;發(fā)送數(shù)據(jù)之后布爾值為false,是鎖定的。若重復(fù)點(diǎn)擊在數(shù)據(jù)沒有到來之前也就是布爾值為true時(shí),會(huì)把重復(fù)點(diǎn)擊忽略。
//利用布爾值作為狀態(tài)鎖
var lock = true;
btn.addEventListener('click',function(){
//用戶重復(fù)點(diǎn)擊,數(shù)據(jù)沒有到來之前直接return,忽略重復(fù)點(diǎn)擊
if(!lock ){
return;
}
ajax({
...
//數(shù)據(jù)到來,布爾值設(shè)為true
lock = true;
})
xhr.open(...,...,...);
xhr.send();
//發(fā)送ajax請求,這時(shí)數(shù)據(jù)還沒有到來,布爾值設(shè)為false
lock = false;
});
9、封裝AJAX實(shí)現(xiàn)加載更多
這里使用server-mock來mock數(shù)據(jù)。server-mock是一款nodejs命令行工具,用于搭建web服務(wù)器,模擬網(wǎng)站后端,方便前端開發(fā)者M(jìn)ock數(shù)據(jù)。
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<style>
ul,li{
margin; 0;
padding: 0
}
#ct li{
list-style: none;
border:1px solid #ccc;
padding:10px;
margin-top: 10px;
cursor:pointer;
}
#load-more{
display: block;
margin:10px auto;
text-align: center;
cursor: pointer;
}
.btn{
display: inline-block;;
height: 40px;
line-height: 40px;
width: 80px;
border: 1px solid #E27272;
border-radius: 3px;
text-align: center;
text-decoration: none;
color:#E27272;
}
.btn:hover{
background: green;
color:#fff;
}
</style>
</head>
<body>
<ul id="ct">
</ul>
<a id="load-more" class="btn" href="#">
加載更多
</a>
<script>
var btn = document.querySelector('#load-more');
var ct = document.querySelector('#ct');
var pageIndex = 0;
//設(shè)置狀態(tài)鎖,防止數(shù)據(jù)到來之前用戶重復(fù)點(diǎn)擊
var isDataArrive = true;
btn.addEventListener('click',function (e) {
e.preventDefault();
if (!isDataArrive) {
return;
}
loadData(function(news){
renderPage(news);
})
})
function loadData(callback){
ajax({
type: 'get',
url: '/loadMore',
data: {
index: pageIndex,
length: 5
},
onSuccess: callback,
onError: function(){
console.log('出錯(cuò)了')
}
})
}
function renderPage(results){
var fragment = document.createDocumentFragment();
for (var i = 0; i < results.length; i++) {
var node = document.createElement('li');
node.innerText = results[i];
fragment.appendChild(node);
}
ct.appendChild(fragment)
}
function ajax(options){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr.status === 304) {
//與后端約定好,傳輸?shù)臄?shù)據(jù)類型為JSON字符串,JSON.parse()用來把JSON字符串解析為原生JavaScript值
var results = JSON.parse(xhr.responseText);
options.onSuccess(results);
pageIndex = pageIndex + 5;
}else{
options.onError();
}
//數(shù)據(jù)到來,布爾值設(shè)為true
isDataArrive = true
}
}
var str = '';
for(var key in options.data){
str += key + '=' + options.data[key] + '&';
}
str = str.substr(0, str.length-1);
if(options.type.toLowerCase() === 'get'){
xhr.open(options.type, options.url + '?' + str, true)
xhr.send()
}
if(options.type.toLowerCase() === 'post'){
xhr.open('post', options.url, true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send(str);
}
//發(fā)送ajax請求,這時(shí)數(shù)據(jù)還沒有到來,布爾值設(shè)為false
isDataArrive = false;
}
</script>
</body>
</html>
router.js
app.get('/loadMore',function(req,res){
var curIdx = req.query.index;
var len= req.query.length;
var data = [];
for (var i = 0; i < len; i++) {
data.push('新聞' + (parseInt(curIdx) + i))
}
res.send(data);
})
app.post('/loadMore',function(req,res){
var curIdx = req.body.index;
var len= req.body.length;
var data = [];
for (var i = 0; i < len; i++) {
data.push('新聞' + (parseInt(curIdx) + i))
}
res.send(data);
})



每次點(diǎn)擊加載更多按鈕都會(huì)發(fā)送一條AJAX請求,數(shù)據(jù)沒回來之前,重復(fù)點(diǎn)擊會(huì)被忽略,數(shù)據(jù)到來后會(huì)渲染到頁面上出現(xiàn)5條新聞。