无论是在真实的项目开发中,还是面试的问题中,对前端的优化处理方式都十分看重,而面对真实的长列表,面对成千上万的数据列表,要怎么去渲染呢?
首先最常规的就是前,后端进行约定分页处理。假如有1000条数据,前端在利用ajax发送一些必要数据后,需要在加上总页数,和当前页数,那么此时,如果约定是10页的话,那么现在前端只是获取的当页的10条数据。跳转不同页面之后,获取不同页面的10条数据。这是最简单一种处理方式,不过这种方式也有它明显的弊端,假如要跨页寻找数据时候,就需要开辟新的存储空间去存,在实际情况中,甚至存在不能分页的情况,这时候要怎么进行优化呢?
先引入一种时间切片的方式,
时间切片的核心思想是:如果存在一个长任务不能在50毫秒内执行完(10万条数据的加载渲染),那么为了不阻塞主线程,这个任务应该让出主线程的控制权,使浏览器可以处理其他任务。让出控制权意味着停止执行当前任务,让浏览器去执行其他任务,随后再回来继续执行没有执行完的任务。
//需要插入的容器
let ul = document.getElementById('container');
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
//总页数
let page = total/once
//每条记录的索引
let index = 0;
//循环加载数据
function loop(curTotal,curIndex){
if(curTotal <= 0){
return false;
}
//每页多少条
let pageCount = Math.min(curTotal , once);
setTimeout(()=>{
for(let i = 0; i < pageCount; i++){
let li = document.createElement('li');
li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
ul.appendChild(li)
}
loop(curTotal - pageCount,curIndex + pageCount)
},0)
}
loop(total,index);
如上述代码所示,我们一次利用setTimeout加载20条,每次setTimeout是一次宏任务,在浏览器的执行机制中,没进行一轮的宏任务之后会进行渲染,这样的话,就可以实现,每加载20条数据后渲染,不会造成主线程一直被占用的情况。
第二种方式是虚拟列表
这种方法的核心思想就是,现将所有的数据得到,但是只去渲染视窗可见部分的数据。话不多说,上图
这一共有1000条数据,我们一次只渲染其中可见的10条。如图现在显示0 - 9, 随着滚轮往下移动,我们去渲染当时屏幕可以看见的那10条数据。也就是说同一个时间内也只是显示10条数据从而减少了对dom的操作,优化了性能。
来分析一下代码
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style type="text/css" rel="stylesheet">
//分析css部分
.content{
background-color: orangered;
overflow:auto;
//这里是为了出现滚轮
width: 50vw;
height: 500px;
//一条数据50px高,可视窗口500px,一页可以放10条数据
margin-left: 25vw;
}
.target{
height: 50000px;
//整个滑轮可以滚动的长短也就是可以存储1000条数据
box-sizing: border-box;
//这里需要调整一下盒子模型,后续要配合padding-top属性设置偏移量。
}
</style>
</head>
<body>
<div class="content" >
<!-- div标签是可视窗口 -->
<ul class ="target">
<!-- ul标签是滑动总长度 -->
<!-- li标签放入其中,用于存放单挑数据 -->
</ul>
</div>
</body>
<script>
let datalength = 10;
//每页显示数据
let data = [];
//总共的1000条数据,数组存放
let showdata = [];
//要去渲染的10条数据,数组存放
let topdistance = 0;
//记录偏移量
let first;
//首条数据的索引
let end;
//尾条数据索引
for(let i = 0;i<1000;i++){
data.push(i);
}
let div = document.querySelector('.content');
let ul = document.querySelector('ul');
myscroll()
//首屏渲染数据
div.onscroll = myscroll
function myscroll(e){
ul.innerHTML = '';
//每次渲染之前清空内容
topdistance = e?e.target.scrollTop:0;
//手动设置偏移量
ul.style.paddingTop = topdistance+'px';
//将偏移量赋值给paddingTop,注意上述css盒模型如果不设置一下的话,会导致滚轮一直下滑,ul的高度会被无限拉长
let index = Math.floor(topdistance/50);
//根据偏移量判断该从哪里截取
first = index;
end =index+datalength;
showdata = data.slice(first,end);
//对原生数据进行截取
const dom = document.createDocumentFragment();
let li = null;
//下面操作dom节点进行渲染
showdata.map((item)=>{
li = document.createElement('li');
li.style.height = '50px';
li.innerHTML = item;
dom.appendChild(li);
})
ul.appendChild(dom);
}
</script>
</html>