高效前端优化实践: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前请联系博主,并注明本文链接!

猜你喜欢

转载自blog.csdn.net/qq_43624878/article/details/106747078