高效前端優化實踐:Ajax請求優化新體驗

說起來ajax,相信諸位都不會陌生 —— 這可是當下前端最火的技術之一。

ajax請求數據,將數據拿到前端頁面上,通過一定手段展示給用戶,造成“不必刷新頁面”的局部數據刷新。這是ajax最主要的功能。
一直以來使用ajax都是“橫衝直撞”:不管三七二十一,調用就完了。拿到數據後也是直接放到頁面區域上

——一般來說沒有什麼問題,直到遇見了分頁:一個大數據量重複請求的場景。

於是,我們對ajax的使用做了優化:
(本文所說皆基於“切換分頁”場景)
fg

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;
		}
	});
}

這個思路在“切換分頁”這個場景下用的非常多 —— 每次切換時都要將當前頁的數據存到緩存中,每次切換時都先去看一下要去的“上一頁”或“下一頁”的數據是否在緩存中(之前是否切換到此也過),做不同的處理。

fg

使用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前請聯繫博主,並註明本文鏈接!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章