当前位置: 首页 > Web前端 > JavaScript

面试题:渲染10万条数据的解决方法

时间:2023-03-27 13:34:11 JavaScript

笔者在之前的面试中遇到了“我一次性给你1万条数据,如何让它不卡顿”的问题。要回答这类问题,提到的解决方案过于简单,还涉及到防抖、节流等性能优化点。这篇文章原计划写在2022年1月29日,但是因为春节的缘故推迟了。发现有3种方案:虚拟列表(也叫按需渲染或者可见区域渲染)延迟渲染(也就是惰性渲染)时间分片虚拟列表是最主流的方案,不渲染所有数据,只渲染区域内可见的数据。当用户滑动(滚动)时,监听滚动判断是向上滑动还是向下滑动,从而更新数据。同样,IntersectionObserver和getBoundingClientRect都可以实现延迟渲染,也就是所谓的延迟加载。顾名思义,一开始并不渲染所有数据,只渲染可视区域的数据(与虚拟列表一致)。滚动到页面底部时,添加数据(concat),添加DOM进行视图渲染。时间分片主要是批量渲染DOM,使用requestAnimationFrame让动画更流畅。先说最主流的解决方案虚榜。什么是虚拟列表?按需显示的一种实现方式,即只渲染可见区域,不渲染或部分渲染不可见区域的数据。是一种针对长列表渲染的优化方法。就是显示可见区域。上下滚动时,通过DOMAPI替换可见区域的数据,从而动态加载10万条数据。两种解决方案关于无限滚动,最常见的解决方案是在早期计划中监听滚动事件。可以去图片懒加载查看。简单来说就是通过子项的offsetTop(偏移高度)与innerHeight(窗口高度)+scrollTop(滚动高度)的比较来实现的。当偏移高度<窗口高度+滚动高度时,描述已经滚动到底部,可以显示图片。在图片的懒加载中,我们也提到了IntersectionObserver(交叉观察者)API来解决scroll没有的效果,即IntersectionObserverAPI是异步的,不跟随目标元素。滚动是同步触发的,性能消耗小。当然也可以通过getBoundingClientRect来实现。getBoundingClientRect方法返回元素的大小和机器相对于窗口的位置。PS:所以目前有3种方法,单独使用这三种方案的代码会在文末的demo中附在这里,所以我的力量有限,并没有解决getBoundingClientRect时页面抖动的问题向上滑动。解决方案只有两种:scroll和IntersectionObserver(getBoundingClientRect方法也放在代码里,但是向上滑动会晃动),就是将它发送过来的数据拆分显示,使用slice方法,会返回一个新的数组。我们假设单个列表的高度为30px,页面显示的列表个数为constcount=Math.ceil(listheight/30),显示的数据为visibleData=data.slice(start,start+count)(start开始为0)滚动时,动态修改start和visibleDataimportconst[开始,setStart]=useState(0);const[visibleCount,setVisibleCount]=useState(null);const[visibleData,setVisibleData]=useState([]);constvirtualRef=useRef(null);constvirtualContentRef=useRef(null);useEffect(()=>{constcount=Math.ceil(virtualRef.current.clientHeight/30);setVisibleCount(count);setVisibleData(data.slice(start,start+count));},[]);constonHandleScroll=()=>{constscrollTop=virtualRef.current.scrollTop;constfixedScrollTop=scrollTop-(scrollTop%30);virtualContentRef.current.style.webkitTransform=`translate3d(0,${fixedScrollTop}px,0)`;setStart(Math.floor(scrollTop/30));setVisibleData(data.slice(start,start+visibleCount));};返回(

{visibleData.map(item=>({item.key}{item.value}
))}
);};导出d默认虚拟列表;注意:virtual-list-phantom会让滚动条看起来很高。个人认为不会影响观感(withvirtual-list-phantom)效果如下:(withoutvirtual-list-phantom)效果如下:This这个方法的本质是设置开始渲染的点和显示的数据,在滚动的时候会动态修改,但是因为滚动会频繁触发,当渲染的数据变大的时候会有性能问题。IntersectionObserver方案利用了IntersectionObserver的特性。当target对象中的entry.isIntersecting为true或者intersectionRatio>0(该元素与祖先元素相交且可见)时,表示该不可见元素漂浮在视图中,表示它向上滑动或向下滑动,我们动态设置top,可以通过bottomid判断向下滑动时,entry.traget.id==='bottom',我们修改start和end;同理,向上滑动时,entry.traget.id==='top,我们也修改附加的start和end部分代码:...const[start,setStart]=useState(0);const[end,setEnd]=useState(THRESHOLD);const[observer,setObserver]=useState(null);constbottomElement=useRef();consttopElement=useRef();...constObserver=newIntersectionObserver(callback,options);constcallback=(entries,observer)=>{entries.forEach(entry=>{constdataLength=data.length;if(entry.isIntersecting&&entry.target.id==="bottom"){constmaxStartIndex=dataLength-1-阈值;constmaxEndIndex=dataLength-1;constnewStart=end-5<=maxStartIndex?end-5:maxStartIndex;constnewEnd=end+10<=maxEndIndex?end+10:maxEndIndex;setStart(newStart);setEnd(newEnd);}if(entry.isIntersecting&&entry.target.id==="top"){constnewEnd=end===阈值?阈值:结束-10>阈值?结束-10:阈值;constnewStart=开始===0?0:开始-10>0?开始-10:0;设置开始(新开始);设置结束(新结束);}});};constupdatedList=data.slice(start,end);return({updatedList.map((item,index)=>{consttop=height*(index+start)+"px";constrefVal=getReference(index,index===lastIndex);constid=index===0?"top":index===lastIndex?"bottom":"";return();})});...效果如下:推荐使用该方法。IntersectionObserver是一个低性能消耗的异步API。缺点是一些落后的浏览器不支持,如果公司需要兼容这类用户,需要引入polyfill懒加载。不多介绍,一句话解释:一开始,所有的数据都不渲染,只展示视图上可见的数据。滚动到页面底部时,加载更多数据实现原理:通过监听父元素的scroll事件,当然也可以通过IntersectionObserver或者getBoundingClientRect等API实现,但是scroll事件会被触发频繁,需要手动节流;滚动元素中有很多DOM,容易卡顿,推荐使用IntersectionObserver,因为之前讲图片懒加载的时候有提到这个思路,这里就不贴了。文末附上demo时间片,参考如何高性能渲染10万条数据(时间片)。举个例子,当渲染大量数据时,JSComputing不是性能的瓶颈,性能的瓶颈主要在渲染阶段也就是说JS执行速度非常快,同时渲染大量DOM导致页面卡顿,可以通过批量渲染解决letul=document.getElementById('container');//Insert100,000itemsDatalettotal=100000;//一次插入20条记录letonce=20;//总页数letpage=total/once//每条记录的索引letindex=0;//循环加载数据函数loop(curTotal,curIndex){if(curTotal<=0){returnfalse;}//每页有多少项目letpageCount=Math.min(curTotal,once);setTimeout(()=>{for(leti=0;i{},0),但这是不准确的。按照H5标准,setTimeout的第二个参数不能小于4ms,不足会自动增加。所以当第一个宏任务完成后,第一个微任务完成,第一次渲染页面,4毫秒后执行第二个宏任务,导致实际执行时间慢了4毫秒。当一个周期的总时间(宏任务+微任务+GUI渲染+4ms)大于16.7ms时,就会出现掉帧现象,这也是React采用Fiber架构的原因。另外,各种电子设备的刷新频率不同,也会导致一个周期的总时间大于16.7ms。requestAnimationFrame是解决这个问题的关键API,它告诉浏览器——你要执行一个动画,要求浏览器在下次重绘前调用指定的回调函数来更新动画。因为它是浏览器提供的原生API,所以可以被各种电子设备使用。根据不同的刷新率,给定不同的动画执行次数,不会造成丢帧...functionloop(curTotal,curIndex){if(curTotal<=0){returnfalse;}//每页有多少项目letpageCount=Math.min(curTotal,once);window.requestAnimationFrame(function(){for(leti=0;i
最新推荐
猜你喜欢