震惊!后端大哥偷懒不分页,前端如何在不影响性能时加载大量数据
原文:刘小夕
1 2 3 4 5
| <div> <ul id="appScrollData"></ul> </div> <div id="app"></div> <div id="poll"></div>
|
初级方案,直接渲染
数据量庞大将导致页面性能极具降低, 造成页面卡顿
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| function getData() { return new Promise((res, rej) => {}) }
function way1() { getData().then(res => { console.log('开始创建') console.time('普通创建对象') let box = document.createElement("ul"); for (let i = 0; i < res.data.length; i++) { let div1 = document.createElement("li"); div1.innerHTML = `${res.data[i].title} ${res.data[i].name} ${res.data[i].text}` box.appendChild(div1) } console.log(box, res.data) app.appendChild(box) setTimeout(() => { console.timeEnd('普通创建对象') }) }) }
function way2() { getData().then(res => { console.log('开始创建') console.time('文档碎片创建对象') var box = document.createDocumentFragment(); for (let i = 0; i < res.data.length; i++) { let div1 = document.createElement("li"); div1.innerHTML = `${res.data[i].title} ${res.data[i].name} ${res.data[i].text}` box.appendChild(div1) } console.log(box, res.data) app.appendChild(box) setTimeout(() => { console.timeEnd('文档碎片创建对象') }) }) }
|
这里用到了 console.time/console.timeEnd 计算函数运行时长
注意为什么console.timeEnd
放在了setTimeout
中?
事件循环先宏任务,再微任务,最后更新ui
setTimeout属于宏任务
这里需要得到能够渲染数据后的运行时长,所以放在了下一轮宏任务中,能够准确获取总
1 2 3 4
| // 10w数据 普通 文档碎片 // chrome 10521.47412109375ms 12217.3671875ms // edge 8409.425048828125ms 8900.69287109375ms // firefox 828ms 卡崩溃了
|
(createDocumentFragment真的更快吗?大家可以测试告诉我)
中级方案,懒加载+分页
渲染首屏,后续窗口滚动时防抖优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| let getApiData = []; let way3Data;
let data = []; let curPage = 1; let pageSize = 30; let prevY = 0;
function way3() { getData().then(res => { getApiData = res.data; }) } function debounce(fn, delay) { let time = ''; return function(...args) { clearTimeout(time); time = setTimeout(() => { fn.apply(this, args); }, delay) } }
function scrollAndLoading() { if (window.scrollY > prevY) { prevY = window.scrollY; if (poll.getBoundingClientRect().top <= window.innerHeight) { if (getApiData.length == 0) return console.log('getApiData数据还未请求返回'); curPage++; way3Data = getApiData.slice(pageSize * (curPage - 1), pageSize * curPage) console.log('开始创建', way3Data.length / getApiData.length) console.time('文档碎片创建对象') var box = document.createDocumentFragment(); for (let i = 0; i < way3Data.length; i++) { let div1 = document.createElement("li"); div1.innerHTML = `${way3Data[i].title} ${way3Data[i].name} ${way3Data[i].text}` box.appendChild(div1) } console.log(box, way3Data) app.appendChild(box) setTimeout(() => { console.timeEnd('文档碎片创建对象') }) } } }
|
高级方案,更优雅的实现方式
- js缓冲器来分片处理
- 虚拟长列表 核心思路就是每次只渲染可视区域的列表数,当滚动后动态的追加元素并通过顶部padding来撑起整个滚动内容
- web worker来将需要在前端进行大量计算的逻辑移入进去, 保证js主进程的快速响应, 让web worker线程在后台计算
- 模糊搜索,二分法优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| function multistep(steps, taskHandle, callback) { var tasks = steps; setTimeout(function() { var task = tasks.shift(); taskHandle(task);
if (tasks.length > 0) { setTimeout(arguments.callee, 25); } else { callback(); } }, 25); }
function way4() { getData().then(res => { getApiData = res.data; multistep(getApiData, function(_arr) { let box = document.createDocumentFragment(); let div1 = document.createElement("li"); div1.innerHTML = `${_arr.title} ${_arr.name} ${_arr.text}`; box.appendChild(div1); app.appendChild(box); }, function() { console.log('way4完成') }) }) }
|
这里用分片处理,每个事件循环周期处理几十条数据,每轮5ms
步骤的集合,每次执行的依赖参数,完成结果回调
无论采取什么方案,最终目的是释放主线程,让用户可进行交互操作时,无卡顿感知