[Lv3] 虚拟滚动实现:处理大量数据渲染
当页面需要渲染 1000+ 条数据时,虚拟滚动可以将 DOM 节点从 1000+ 降至 20-30 个,内存使用降低 80%。
面试情境题
Q: 页面上的 table 有不止一个,且如果各自有超过一百条数据,同时又有频繁更新 DOM 的事件,会用什么方法去优化这页的性能?
问题分析(Situation)
实际项目场景
在平台项目中,我们可能有页面需要处理大量数据:
📊 某页面历史记录页面
├─ 充值记录表格:1000+ 条
├─ 提款记录表格:800+ 条
├─ 投注记录表格:5000+ 条
└─ 每条记录 8-10 个字段(时间、金额、状态等)
❌ 未优化的问题
├─ DOM 节点数:1000 条 × 10 字段 = 10,000+ 个节点
├─ 内存占用:约 150-200 MB
├─ 首次渲染时间:3-5 秒(白屏)
├─ 滚动卡顿:FPS < 20
└─ WebSocket 更新时:整个表格重新渲染(非常卡)
问题严重性
// ❌ 传统做法
<tr v-for="record in allRecords"> // 1000+ 条全部渲染
<td>{{ record.time }}</td>
<td>{{ record.amount }}</td>
// ... 8-10 个字段
</tr>
// 结果:
// - 初始渲染:10,000+ 个 DOM 节点
// - 用户实际可见:20-30 条
// - 浪费:99% 的节点用户根本看不到
解决方案(Action)
Virtual Scrolling(虚拟滚动)
先考虑虚拟滚动的优化问题,从这个角度出发,大概有两个方向,一个是选择官方背书的第三方套件 vue-virtual-scroller,根据参数和需求,来决定可视范围的 row。
// 只渲染可见区域的 row,例如:
// - 100 条数据,只渲染可见的 20 条
// - 大幅减少 DOM 节点数量
另一种则是选择自己手写,但考虑到实际开发的成本,以及涵盖的情境,我应该会比较倾向采用官方背书的第三方套件。
数据更新频率控制
解法一:requestAnimationFrame(RAF) 概念:浏览器每秒最多重绘 60 次(60 FPS),更新再快人眼也看不到,所以我们配合屏幕刷新率更新
// ❌ 原本:收到数据就立刻更新(每秒可能 100 次)
socket.on('price', (newPrice) => {
btcPrice.value = newPrice;
});
// ✅ 改良:收集数据,配合屏幕刷新率一次更新(每秒最多 60 次)
let latestPrice = null;
let isScheduled = false;
socket.on('price', (newPrice) => {
latestPrice = newPrice; // 暂存最新价格
if (!isScheduled) {
isScheduled = true;
requestAnimationFrame(() => {
btcPrice.value = latestPrice; // 在浏览器准备重绘时才更新
isScheduled = false;
});
}
});
解法二:throttle(节流) 概念:强制限制更新频率,例如「每 100ms 最多更新 1 次」
// lodash 的 throttle(如果项目有用)
import { throttle } from 'lodash-es';
const updatePrice = throttle((newPrice) => {
btcPrice.value = newPrice;
}, 100); // 每 100ms 最多执行 1 次
socket.on('price', updatePrice);