假设现在后端给你 10 万条数据,你如何优雅的展示在页面?
虽然现实中这种是不可能的,但是有些面试会问,这里个人总结了八种方法,但是我只写了五种
准备工作,简单搞个架子
<! DOCTYPE html >
< html lang = " en" >
< head>
< meta charset = " UTF-8" />
< meta http-equiv = " X-UA-Compatible" content = " IE=edge" />
< meta name = " viewport" content = " width=device-width, initial-scale=1.0" />
< title> Document</ title>
< style>
* {
padding : 0;
margin : 0;
}
#box {
height : 180vh;
}
.item {
display : flex;
padding : 10px;
}
img {
width : 150px;
height : 150px;
}
</ style>
</ head>
< body>
< div id = " box" > </ div>
< script> </ script>
</ body>
</ html>
下面先写一个获取数据的函数
function getList ( ) {
return new Promise ( ( resolve, reject ) => {
setTimeout ( ( ) => {
try {
let list = [ ]
for ( var i = 0 ; i < 100000 ; i++ ) {
list. push ( {
img : './img.webp' ,
text : '我是' + i + 1 + '号嘉宾' ,
num : i + 1 ,
} )
}
resolve ( list)
} catch ( err) {
reject ( )
}
} , 10 )
} )
}
const box = document. getElementById ( 'box' )
方法一
最简单粗暴的方式直接渲染 以我电脑的配置要渲染十多秒,浏览器还很卡,走路都不利索
getList ( ) . then ( ( res ) => {
console. time ( '开始时间' )
res. forEach ( ( item ) => {
var div = document. createElement ( 'div' )
div. className = 'box'
div. innerHTML = ` <img src=" ${ item. img} " /><span> ${ item. text} </span> `
box. appendChild ( div)
} )
console. timeEnd ( '结束时间' )
} )
方法二
setTimeout 分页渲染,分批渲染,这样可以提高首屏的加载速度,(异步队列渲染,提高渲染速度和性能,用户几乎感觉不到,浏览器也不卡了,腰也不痛了走路也利索了,嘿嘿)
const randerDom = async ( ) => {
console. time ( '开始时间' )
var list = await getList ( )
var total = list. length
var page = 0
var pageSize = 100
var totalPage = Math. ceil ( total / pageSize)
var rander = ( page ) => {
if ( page >= totalPage) return console. timeEnd ( '结束时间' )
setTimeout ( ( ) => {
for ( var i = page * pageSize; i < page * pageSize + pageSize; i++ ) {
var item = list[ i]
var div = document. createElement ( 'div' )
div. className = 'item'
div. innerHTML = ` <img src=" ${ item. img} " /><span> ${ item. text} </span> `
box. appendChild ( div)
}
rander ( page + 1 )
} , 0 )
}
rander ( page)
}
randerDom ( )
方法三 使用 requestAnimationFrame 来代替定时器 湿滑的一批
为什么要用 requestAnimationFrame 代替定时器,setTimeout:通过设定间隔时间来不断改变图像位置,达到动画效果。但是容易出现卡顿、抖动的现象;原因是:1、settimeout 任务被放入异步队列,只有当主线程任务执行完后才会执行队列中的任务,因此实际执行时间总是比设定时间要晚;2、settimeout 的固定时间间隔不一定与屏幕刷新时间相同,会引起丢帧。requestAnimationFrame 的优势, requestAnimationFrame:优势:由系统决定回调函数的执行时机。60Hz 的刷新频率,那么每次刷新的间隔中会执行一次回调函数,不会引起丢帧,不会卡顿,内容过多,有兴趣可以去这篇文章靠看看 点这里点这里,嘿嘿 , 不废话了,上代码
const randerDom1 = async ( ) => {
console. time ( '开始时间' )
var list = await getList ( )
var total = list. length
var page = 0
var pageSize = 100
var totalPage = Math. ceil ( total / pageSize)
var rander = ( page ) => {
if ( page >= totalPage) return console. timeEnd ( '结束时间' )
window. requestAnimationFrame ( ( ) => {
for ( var i = page * pageSize; i < page * pageSize + pageSize; i++ ) {
var item = list[ i]
var div = document. createElement ( 'div' )
div. className = 'item'
div. innerHTML = ` <img src=" ${ item. img} " /><span> ${ item. text} </span> `
box. appendChild ( div)
}
rander ( page + 1 )
} , 0 )
}
rander ( page)
}
randerDom1 ( )
方法四 使用文档碎片+requestAnimationFrame 的方式实现, 这个就有水平多了
前面的方法都是创建一次就插入一次, 这样每次都会重排(回流)重绘,用文档碎片就不一样了,可以先把 1 页的 div 标签先放进文档碎片中,然后一次性 appendChild 到 container 中,这样减少了 appendChild 的次数,极大提高了性能
console. time ( '开始时间' )
var list = await getList ( )
var total = list. length
var page = 0
var pageSize = 100
var totalPage = Math. ceil ( total / pageSize)
var rander = ( page ) => {
if ( page >= totalPage) return console. timeEnd ( '结束时间' )
window. requestAnimationFrame ( ( ) => {
const fragment = document. createDocumentFragment ( )
for ( var i = page * pageSize; i < page * pageSize + pageSize; i++ ) {
var item = list[ i]
var div = document. createElement ( 'div' )
div. className = 'item'
div. innerHTML = ` <img src=" ${ item. img} " /><span> ${ item. text} </span> `
fragment. appendChild ( div)
}
box. appendChild ( fragment)
rander ( page + 1 )
} , 0 )
}
rander ( page)
方法五 滚动懒加载+文档碎片, 都是大佬 就不搞注释了
var pageObj = {
total : 0 ,
page : 0 ,
pageSize : 100 ,
totalPage : 0 ,
}
var data = [ ]
const getData = ( ) => {
var page = pageObj. page * pageObj. pageSize
var pageSize = page + pageObj. pageSize
var list = data. slice ( page, pageSize)
const fragment = document. createDocumentFragment ( )
list. forEach ( ( item ) => {
var div = document. createElement ( 'div' )
div. className = 'item'
div. innerHTML = ` <img src=" ${ item. img} " /><span> ${ item. text} </span> `
fragment. appendChild ( div)
} )
box. appendChild ( fragment)
}
getList ( ) . then ( ( res ) => {
data = res
pageObj. total = res. length
pageObj. totalPage = Math. ceil ( pageObj. total / pageObj. pageSize)
getData ( )
} )
window. addEventListener ( 'scroll' , function ( event ) {
var scrollTop =
document. documentElement. scrollTop ||
window. pageYOffset ||
document. body. scrollTop
if (
document. documentElement. scrollHeight <=
document. documentElement. clientHeight + scrollTop
) {
if ( pageObj. page == pageObj. totalPage) return
pageObj. page++
getData ( )
}
} )
方法六
手动分页列表 就跟pc端管理系统那种tab分页, 这里就不写了,
方法七
滚动懒加载, 之前的几种方式最终dom上都会有10w个div 这样页面会变得很卡,目标: 页面上只放固定的100或者200个div,div数量不变, 只切换数据 ,page的计算 删除那些数据,添加那些数据 (还没有思路)
方法八
虚拟dom 这个,这个 ,这 , 这, 先把vue的虚拟dom搞明白在来研究这个