假设现在后端给你 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搞明白在来研究这个