一.前言
想必大家在面试的时候会经常被问到后端一下返回十万条数据前端怎么进行处理,二面问。这是一个有关项目优化的问题。前面我写了一篇关于时间分片(数据分割)和滚动触底加载的文章,在这我就不写了,感兴趣的大家可以去看看,有源码,很详细。
二.为嘛这个面试题的答案是虚拟列表?
首先遇到这种大数据处理,肯定想到的是数据懒加载,那就是虚拟列表、时间分片、滚动加载,当然最常用的方法是分页,但是产品经理他不想让你这么干,最近项目优化用到这个方案,详细总结一下
时间分片:通过定时器在规定时间内更新数据,数据是一种伪实时性的,给人一种感觉是实时更新,其实不是,因为你定时器时间跟websocket推送数据的时间间隔不一样。而且时间分片约到后面页面越卡,真实项目经验。
滚动条触底加载:通过监听滚动条时间,只要触底就进行调接口返回数据。一下返回十万条数据真渲染不出来,而且你不能一触底就调接口,这样也是耗费性能的,所以滚动条触底加载只适合少量数据
虚拟列表:只对可见区域的数据进行渲染,对非可见区域的数据只做缓存,不渲染。以此来减少性能消耗,提高用户体验。它是长列表的一种优化方案。性能可以说是数据懒加载中的最优化方案。
三.虚拟列表的实现思路(重点:面试过程中就这么回答)
1.首先你得写一个div,这个div得固定高度。通过css的overflow属性使其允许纵向y轴滚动。
2.计算可视区域内可以显示的数据条数。可以用可视区域的高度除以一条数据的高度。
3.监听滚动条,当滚动条滚动时计算出被卷起的数据的高度。
4.计算这个可视区域内数据的起始索引值,也就是第一条数据的下标。通过滚动条卷起的高度除以单条数据的高度,这块可能大家一下反应不过来,起始坐标最开始是0对吧。卷起的高度除以单条数据的高度是不是就是卷起的数据的数量,那么他的索引值是不是就是数据的数量。假如被卷起四条数据,那么第五条数据索引值是不是四。
5.计算区域结束数据的索引。通过起始索引加上可视区域显示的数据条数
6.截取数据渲染到可视区域。从起始索引截取到结束索引。
7.计算起始索引在整个列表中的偏移位置并设置到列表上,因为每次都不可能滚动到整条数据上。
这样一个基本的虚拟列表就有了,当然大家可以根据需求做一些性能优化,比如防抖节流,如果用户一直快速滚动问题。
四.小demo.(代码+注释)
<template>
<div :style="{ height: `${contentHeight}px` }" class="content_box" @scroll="scroll">
<!--这层div是为了把高度撑开,让滚动条出现,height值为所有数据总高-->
<div :style="{
'height': `${
itemHeight * (listAll.length)}px`, 'position': 'relative' }">
<!--可视区域里所有数据的渲染区域-->
<div :style="{
'position': 'absolute', 'top': `${
top}px` }">
<!--单条数据渲染区域-->
<div v-for="(item, index) in showList" :key="index" class="item">
{
{
item }}
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "list",
data () {
return {
listAll: [], //所有数据
showList: [], //可视区域显示的数据
contentHeight: 500, //可视区域高度
itemHeight: 30, //每条数据所占高度
showNum: 0, //可是区域显示的最大条数
top: 0, //偏移量
scrollTop: 0, //卷起的高度
startIndex: 0, //可视区域第一条数据的索引
endIndex: 0, //可视区域最后一条数据后面那条数据的的索引,因为后面要用slice(start,end)方法取需要的数据,但是slice规定end对应数据不包含在里面
}
},
methods: {
//构造10万条数据
getList () {
for (let i = 0; i < 100000; i++) {
this.listAll.push(`我是bug天选之子的第${
i}数据呀`)
}
},
//计算可视区域数据
getShowList () {
this.showNum = Math.ceil(this.contentHeight / this.itemHeight); //可视区域最多出现的数据条数,值是小数的话往上取整,因为极端情况是第一条和最后一条都只显示一部分
this.startIndex = Math.floor(this.scrollTop / this.itemHeight); //可视区域第一条数据的索引
this.endIndex = this.startIndex + this.showNum; //可视区域最后一条数据的后面那条数据的索引
this.showList = this.listAll.slice(this.startIndex, this.endIndex) //可视区域显示的数据,即最后要渲染的数据。实际的数据索引是从this.startIndex到this.endIndex-1
const offsetY = this.scrollTop - (this.scrollTop % this.itemHeight); //在这需要获得一个可以被itemHeight整除的数来作为item的偏移量,这样随机滑动时第一条数据都是完整显示的
this.top = offsetY;
},
//监听滚动事件,实时计算scrollTop
scroll () {
this.scrollTop = document.querySelector('.content_box').scrollTop; //element.scrollTop方法可以获取到卷起的高度
this.getShowList();
}
},
mounted () {
this.getList();
this.scroll();
}
}
</script>
<style scoped>
.content_box {
overflow: auto;
/*只有这行代码写了,内容超出高度才会出现滚动条*/
width: 700px;
border: 1px solid red;
}
/*每条数据的样式*/
.item {
height: 30px;
padding: 5px;
color: #666;
box-sizing: border-box;
}
</style>
```
运行效果: