說起來ajax,相信諸位都不會陌生 —— 這可是當下前端最火的技術之一。
ajax請求數據,將數據拿到前端頁面上,通過一定手段展示給用戶,造成“不必刷新頁面”的局部數據刷新。這是ajax最主要的功能。
一直以來使用ajax都是“橫衝直撞”:不管三七二十一,調用就完了。拿到數據後也是直接放到頁面區域上
——一般來說沒有什麼問題,直到遇見了分頁:一個大數據量重複請求的場景。
於是,我們對ajax的使用做了優化:
(本文所說皆基於“切換分頁”場景)
ajax分頁緩存
這個可是個“明星人物”。它基於這樣一個背景:當你點擊某一頁時,網站會想後臺發一個請求,接收到返回數據後跳過去(局部刷新)。這時,如果沒有設置緩存,那麼原來頁面的數據就不會被瀏覽器“記住”,當你返回這個頁面時瀏覽器會再一次發送請求,甚至當你中途退出後再進來,又回到原來的下標了。這大大加重了瀏覽器和服務器的負擔!
對如下場景:
<div class="wrap flex_column">
<div class="content flex_column"></div>
<div class="page">
<ul class="flex_row">
<!-- <li></li> --> <!-- 這裏實際是後端動態渲染列表 -->
</ul>
</div>
</div>
即是要緩存,我們在獲取元素時就要設置【緩存對象】:
var oContent=document.querySelector('.content');
var oPages=document.querySelector('.page ul li');
var cache={};
changePage(); //運行“檢查數據”函數
檢查數據函數的代碼如下:
function changePage(){
for(let i=0,len=oPages.length;i<len;i++){ //這裏實際要換成從後端ajax過來的長度
if(oPages[i] in cache){
console.log('已經存在了數據');
//...
}else{
console.log('數據還沒有,正在加載中...');
//...
}
console.log(cache);
}
}
可以看到,我們在函數中先進行判斷:當前列表項是否已經在緩存中:如果在緩存中了,那我們就不必再發送ajax請求數據 —— 那樣會增加服務器的壓力,而是直接從緩存中拿到數據:
在上面代碼if中,增加:addDom(cache[i]);
function addDom(result){
var dataList=result;
var dataLength=dataList.length;
var str='';
for(var i=0;i<dataLength;i++){
str+=`
<a href="${dataList[i].url}" class="items flex_row">
<div class="img">
<img src="xxx" alt="" />
</div>
<div class="bd">
<p class="label">${dataList[i].title}</p>
</div>
</a>`
}
oContent.innerHTML=str;
}
反之,我們就要發送ajax從服務器拿到數據:
在else中增加代碼 goTo(page);
function goTo(page){
Ajax({
url:'xxxx',
method:'GET',
data:{
//...
page
},
success:function(res){
var result=JSON.parse(res);
var dataList=result.showapi_res_body.newslist;
//先獲取到我們的數據數組
addDom(dataList);
cache[page]=dataList;
}
});
}
這個思路在“切換分頁”這個場景下用的非常多 —— 每次切換時都要將當前頁的數據存到緩存中,每次切換時都先去看一下要去的“上一頁”或“下一頁”的數據是否在緩存中(之前是否切換到此也過),做不同的處理。
使用H5的history改善Ajax列表請求
既然HTML5的衆多API都是基於瀏覽器原生的,那麼我們從原生的角度來看一下請求數據加載:
當前在點擊“下一頁”時,大部分網頁都採用了動態請求的方式,避免頁面刷新。雖然大家都是ajax,但是從一些小的細節還是可以看出其優劣:比如是否支持瀏覽器“後退”和“前進”鍵(左上角的兩個按鈕)。
數據分頁顯示,早起的做法是在網址後面加個page的參數:在點擊“下一頁”時,讓網頁重定向到page+1的新地址。例如之前新浪的新聞網就是這麼做的。但是這個列表並不是頁面的主體部分!或者說頁面的其他部分也有很多圖片等豐富的元素,例如導航slider —— 再使用這樣的方式,整個頁面會閃爍的很厲害,並且很多資源得重新渲染。
但是話說回來,普通的動態請求不會使網址發生變化。用戶點擊了下一頁,或者點擊了第幾頁,想返回到上一個頁面時,可能去點瀏覽器的返回鍵,這樣就導致了返回的不是原先查看的頁面信息,而是上一個網址了,或者想刷新一下,發現回到了第一頁。
下面以筆者所做的一個案例進行分析:
var pageIndex=0;
function makePage(pageIndex){
var request=new XMLHttpRequest();
request.open("GET","/getBook?page="+pageIndex+"&limit=8",true);
request.send(null);
request.onreadystatechange=statechange;
function statechange(){
if(this.readyState == 4 && this.status == 200){
var books=JSON.parse(request.responseText);
renderPage(books); //渲染數據
}
}
}
拿到數據後進行渲染:
function renderPage(books){
var bookHtml=`<table>`;
for(let i in books){
bookHtml+=`<tr><td>${books[i].name}</td><td>${books[i].author}</td></tr>`;
}
bookHtml+=`</table>`;
bookHtml+=`<button>上一頁</button>
<button οnclick="nextPage()">下一頁</button>`;
var section=document.createElement("section");
section.innerHTML=bookHtml;
document.getElementById("book").appendChild(section);
}
這樣一個基本的ajax請求就搭起來了,然後就是我們分析的重點:“下一頁”按鈕:
function nextPage(){
pageIndex++;
makeRequest(pageIndex);
}
到此,如果不做任何處理的話,就不能夠發揮瀏覽器返回、前進按鈕的作用。
如果能夠檢測到用戶點了後退、前進按鈕的話,就可以做些文章:HTML5就有這麼一個事件 window.onpopstate
,當用戶單擊前進後退按鈕就會觸發這個事件 —— 但是光這樣還不夠,還得傳些參數,因爲這個事件只能檢測到你手動添加上去的url,而且我們返回到之前那個頁面時得知道那個頁面的pageIndex。HTML5裏也有這樣一個函數pushState:
window.history.pushState(state,title,url);
其中state是一個object,爲當前頁面的數據。title沒有什麼用。url爲當前頁面的url —— 一旦更改了這個url,瀏覽器地址欄的地址也會跟着變化(但頁面不會刷新)。
還有就是頁面load事件未觸發之前,點擊後退也不會觸發popstate事件
於是我們這樣做:
function nextPage(){
pageIndex++;
makeRequest(pageIndex);
window.history.pushState({page:pageIndex},null,window.location.href);
}
然後去監聽popstate事件:
window.addEventListener("popstate",function(event){
var page=0;
//state數據通過event傳進來,這樣就可以得到pageIndex
if(event.state !== null){
page=event.state.page;
}
makeRequest(page);
pageIndex=page;
})
這樣就會發現,按鈕被激活了。
到這裏就基本完工了。但是我們發現:在第二頁點擊刷新的話,首先會出現第一頁,點擊下一頁,出現第二頁。然後點返回按鈕,發現還是第二頁,再次點擊時纔會回到第一頁。
打開控制檯,會發現:點第一次返回時獲取到的pageIndex仍然是1,即第二頁。
我們可以理解爲:對history,瀏覽器有一個隊列,用來存放訪問的記錄,包括每個訪問的網址還有state數據。一開始打開頁面,隊列的首指針指向page=0的位置,點下一頁時,執行了pushState,在這個隊列插入了一個元素,隊首指針移向了page=1的位置。同時通過pushState操作記錄了這個元素的url和state數據。
從這裏可以看出,pushState最重要的作用還是給history隊列插入元素,這樣瀏覽器的後退按鈕纔不是置灰的狀態,其次纔是上面所說的存放數據。
那麼當前我們最重要的就是“及時保存(緩存)數據 & 更換pageIndex”:
我們整合一下所用到的函數:
var pageIndex=window.localStorage.pageIndex || 0;
function nextPage(){
window.localStorage,pageIndex=++pageIndex;
makeRequest(pageIndex);
window.history.pushState({page:pageIndex},null,window.location.href);
}
window.addEventListener("popstate",function(event){
var page=0;
//state數據通過event傳進來,這樣就可以得到pageIndex
if(event.state !== null){
page=event.state.page;
}
makeRequest(page);
window.localStorage,pageIndex=page;
})
在改變pageIndex的同時,將其放到localStorage裏,這樣刷新頁面時就可以獲取到當前頁的pageIndex。
還有一種方法是將其放到url參數裏:通過改變當前網址。pageIndex從網址裏面取:
var pageData=window.location.search.match(/page=([^&#]+)/);
var pageIndex=pageData ? +pageData[1] : 0;
function nextPage(){
++pageIndex;
makeRequest(pageIndex);
window.history.pushState({page:pageIndex},null,"?page="+pageIndex);
}
這樣的好處在於,鏈接會跟着變,帶着pageIndex參數的鏈接無論是前端渲染還是服務端渲染都可以實現。並且分享給別人時,打開頁面也會直接看到同步的數據(比如跳轉到第幾頁的數據頁面)。
使用localStorage,唯一擔心的不過就是用戶開啓了隱身/無痕模式(對localStorage的禁用)。
本文靈感及部分研究結果來源於:《高效前端》
本文爲筆者原創,引用或CV前請聯繫博主,並註明本文鏈接!