一、 问题分析
- 设计不合理的
- 后端一次性给到前端大量的数据,本身技术方案设计就不合理
- 应该主动沟通,避免不合理的技术方案
二、 方案选择
- 如果条件允许,前端可以自定义中间层
- 自定义nodejs中间层,获取并拆分这10w条数据
- 前端对接nodejs中间层,而不是服务端
- 成本比较高
- 可以使用虚拟列表
- 只渲染可视区域DOM
- 其它隐藏区域不显示,只用 div 撑起高度
- 随着浏览器滚动,创建和销毁DOM
- 虚拟列表 - 第三方lib
- 虚拟列表实现起来非常复杂,可借用第三方lib,比如
- Vue-virtual-scroll-list
- React-virtualiszed
- 如果,一定要直接渲染,浏览器能否处理10w条数据
- JS计算处理没问题
- 渲染到DOM会非常卡顿
三、如果一定要,直接渲染到浏览器,怎么解决渲染卡顿问题:
-
直接全部循环渲染,整个页面会卡住,渲染完成后恢复,卡住的时间取决于电脑配置(数据量大或者每条数据,也可能会导致浏览器直接崩溃)
const total = 100000; let ul = document.getElementById('container'); let num = 0; for (let i = 0; i < total; i++) { num++; let li = document.createElement('li'); li.innerHTML = num + '-' + Date.now(); ul.appendChild(li); }
-
使用setTimeout把全部一起渲染拆分成每次渲染20条,这样不会因为数据量巨大而崩溃,当然如果电脑配置低,还是会出现卡顿。
const total = 100000; let ul = document.getElementById('container'); let once = 20; let num = 0; function loop(curTotal) { if (curTotal <= 0) return; let pageCount = Math.min(curTotal, once); setTimeout(() => { for (let i = 0; i < pageCount; i++) { num++; let li = document.createElement('li'); li.innerHTML = num + '-' + Date.now(); ul.appendChild(li); } loop(curTotal - pageCount); }); } loop(total);
-
进一步优化,还是拆成20条每次,把setTimeout换成requestAnimationFrame,这样,在渲染的过程中,不会有明显的卡顿。
- setTimeout任务执行的时间是写死的,requestAnimationFrame执行的时机是与屏幕刷新率同步的
const total = 100000; let ul = document.getElementById('container'); let once = 20; let num = 0; function loop(curTotal) { if (curTotal <= 0) return; let pageCount = Math.min(curTotal, once); window.requestAnimationFrame(() => { for (let i = 0; i < pageCount; i++) { num++; let li = document.createElement('li'); li.innerHTML = num + '-' + Date.now(); ul.appendChild(li); } loop(curTotal - pageCount); }); } loop(total);
-
进一步优化性能,把每条数据都执行插入DOM改成每条插入到fragment,每次fragment才插入DOM,那样可以把文档回流的次数减少到1/20
const total = 100000; let ul = document.getElementById('container'); let once = 20; let num = 0; function loop(curTotal) { if (curTotal <= 0) return; let pageCount = Math.min(curTotal, once); window.requestAnimationFrame(() => { let fragment = document.createDocumentFragment(); // 创建一个虚拟文档碎片 for (let i = 0; i < pageCount; i++) { num++; let li = document.createElement('li'); li.innerHTML = num + '-' + Date.now(); fragment.appendChild(li); // 挂到fragment上 } ul.appendChild(fragment); // 现在才回流 loop(curTotal - pageCount); }); } loop(total);