0
点赞
收藏
分享

微信扫一扫

高效前端优化实践:Ajax请求优化新体验


说起来ajax,相信诸位都不会陌生 —— 这可是当下前端最火的技术之一。

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 cache={}, page=0;

changePage(page); //在下一页或上一页或初始时的click事件中运行“检查数据”函数
// 此时page已经+1或-1了

检查数据函数的代码如下:

function changePage(page){
if(cache[page].length){ //这里实际要换成从后端ajax过来的长度
console.log('已经存在了数据');
//...
}else{
console.log('数据还没有,正在加载中...');
//...
}
console.log(cache);
}

可以看到,我们在函数中先进行判断:当前列表项是否已经在缓存中:如果在缓存中了,那我们就不必再发送ajax请求数据 —— 那样会增加服务器的压力,而是直接从缓存中拿到数据:
在上面代码if中,增加:​​​addDom(cache[page]);​

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

这个思路在“切换分页”这个场景下用的非常多 —— 每次切换时都要将当前页的数据存到缓存中,每次切换时都先去看一下要去的“上一页”或“下一页”的数据是否在缓存中(之前是否切换到此也过),做不同的处理。

高效前端优化实践:Ajax请求优化新体验_history-api_02

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

你通过正则取到的pageData是一个数组(两个元素),
第一个元素是“全部匹配”,第二个元素是正则中括号里的值
(比如这段代码的pageData[1]就是没有“page=”的剩余匹配元素)

这样的好处在于,链接会跟着变,带着pageIndex参数的链接无论是前端渲染还是服务端渲染都可以实现。并且分享给别人时,打开页面也会直接看到同步的数据(比如跳转到第几页的数据页面)。

使用localStorage,唯一担心的不过就是用户开启了隐身/无痕模式(对localStorage的禁用)。



举报

相关推荐

0 条评论