当前位置: 首页 > news >正文

前端渲染大量数据思路【虚拟列表】【异步机制】

当浏览器遇到性能瓶颈导致页面卡顿时,你会怎么处理?如何查找问题的原因?

浏览器本身自带性能检测工具,通常我们分析由脚本导致的页面卡顿会选择 性能(performance) 选项卡,在其中我们可以找到导致页面卡顿的函数,另外通过观察我们可以看到脚本的执行时间占比以及页面卡顿的程度。

image.png

此外还有 内存 选项卡,可以截取当前页面的内存快照,由于我们的页面通常不像服务器那样一直运行,所以平常我们也不会过于关注页面的内存使用问题。

1. 渲染大量数据

由于要渲染大量数据,我们刚开始学习前端时,如果经验不足就会简单地将所有数据全部一次性地渲染到页面上,这样势必会造成页面的卡顿。所以这种方法我这里也不会去介绍。

下面直接进入正轨,首先提出一个问题引发思考:如果让你去实现,你会怎么实现?

1.1. 采用异步

我首先是想到如果要渲染大量数据,我们可以将这一个巨大的任务拆分成一个个的小任务来执行,将这一个个的小任务加入到任务队列当中采用异步的方式来慢慢地执行。

不过这个会有个缺陷,由于这么多数据是按照顺序来依次执行的,所以当用户想查看比较靠后的数据时,用户会发现数据一直在加载中、一直在渲染,然后用户等啊等,最后点击关闭标签页。

下面是一个简单的使用异步实现的逻辑(只包含部分代码):

const ul = document.querySelector('ul');let total = 100_000_000;
let count = 0;function loop() {const fragment = document.createDocumentFragment();for (let i = 0; i < 20 && count < total; i++, count++) {const li = document.createElement("li");li.textContent = count;fragment.appendChild(li);}ul.appendChild(fragment);window.requestAnimationFrame(loop);
}loop();

观察上面的代码,其中使用到了 requestAnimationFrame 函数,它接收一个函数作为参数,这个函数会在浏览器每一帧渲染结束之后执行。

为什么不使用 setTimeout 定时器,因为 setTimeout 函数无法控制函数的执行时机,我们只知道函数会被加入到任务队列,但并不知道函数会在何时执行,而 requestAnimationFrame 函数则固定在每一帧渲染之后执行,这样就不会在视觉上让用户感觉页面卡顿。

当然如果 requestAnimationFrame 的函数执行时间过长,会推迟下一帧的渲染,所以尽量不要将耗时任务放在其中。

1.2. 虚拟列表

虚拟列表采用的思想类似于懒加载,都是只加载用户看得见的数据,懒加载在计算机上随处可见,比如单例模式中的饿汉式、图片的懒加载等,在我们常用的 QQ 中,像消息列表,联系人列表他也采用了类似于虚拟列表的形式进行渲染,以减少内存的使用量。

因为这里我们要处理大量数据的渲染,如果要求所有的数据必须全部渲染在可视区当中,那么懒加载就派不上用场了。

虚拟列表主要由可视区的数据构成,其他的位置都是空白。我们可以通过 padding 或者是 top/left 来制造空白。如下图所示(图片来自这篇文章 面试官:如何一次性渲染十万条数据 - 掘金 (juejin.cn)),可视区展示我们想看见的数据,缓存区就是我说的空白。

面试官:如何一次性渲染十万条数据.png

实现的代码如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>* {margin: 0;padding: 0;}.scroll-box {height: 100vh;overflow-y: scroll;}</style>
</head>
<body><div class="scroll-box"><ul></ul></div><script src="./test.js"></script>
</body>
</html>

对应的 test.js:


const ul = document.querySelector('ul');
const scrollBox = document.querySelector('.scroll-box');let total = 100_000_000;
let count = 0;
let liHeight = 20;
// 要展示的数据量
let showCount = scrollBox.clientHeight / liHeight >> 0;let totalHeight = total * liHeight;function generateLi() {// 计算出来的处于可视区顶部的数据索引let index = (scrollBox.scrollTop / liHeight) >> 0;const fragment = document.createDocumentFragment();// 制造空白ul.style.paddingTop = `${index * liHeight}px`;ul.style.paddingBottom = `${(totalHeight - (index + 20) * liHeight)}px`;// 生成可视区数据for (let i = 0; i < showCount; i++) {const li = document.createElement("li");li.textContent = `${index + i}`;fragment.appendChild(li);}// 重新设置元素,这里元素并没有考虑复用ul.innerHTML = "";ul.appendChild(fragment);
}generateLi();scrollBox.addEventListener("scroll", generateLi);

多嘴一句,当前元素可以滚动才有 scrollTop 值,否则一直为 0。比如当前元素固定了高度,但是它的子元素的高度超出了它的高度,就会导致溢出,这是我们可以设置 overflow-y: auto,当前元素就会有滚动条。

而 scrollTop 就是当前元素的顶部与它的子元素的顶部的距离,没有负值。

2. 参考

参考文献:

  • 面试官:如何一次性渲染十万条数据 - 掘金 (juejin.cn)
http://www.lryc.cn/news/369771.html

相关文章:

  • Ubuntu24.04记录网易邮箱大师的安装
  • PDF编辑与转换的终极工具智能PDF处理Acrobat Pro DC
  • Django UpdateView视图
  • 【CS.SE】2024年,你应该选择计算机专业吗?详细分析与未来展望
  • 后端开发面经系列 -- 华为OD -- C++面经(全)
  • 3072. 将元素分配到两个数组中 II Rust 线段树 + 离散化
  • day35|1005.K次取反后最大化的数组和 134. 加油站135. 分发糖果
  • HWA和BSS区别
  • 【Excel】Excel中将日期格式转换为文本格式,并按日期显示。
  • 物联网学习小记
  • 代码随想录-Day29
  • C/C++ 进阶(6)红黑树
  • 【Vue】构建vuex-cart模块
  • 如何成为嵌入式系统工程师?
  • 【AI大模型】Transformers大模型库(七):单机多卡推理之device_map
  • 驱动代码编写(一)
  • Prompt-to-Prompt Image Editing with Cross Attention Control
  • 实验11 OSPF协议配置
  • ChatGPT-4o, 腾讯元宝,通义千问对比测试中文文化
  • node.js学习
  • python将一个图片雕刻镂空成二维码
  • OS进程取样器OS Process Sampler执行CMD/Shell命令
  • excel两个数据表格,怎样实现筛选的联动?
  • python,django好的get和post请求
  • volatile的用法
  • MySQL 与 PostgreSQL 关键对比二(SQL语法)
  • 徐州服务器租用该如何维护?
  • C++习题精选(4)—— 栈
  • Web前端ES6-ES13笔记合集(下)
  • 我要成为算法高手-双指针篇