大家好,我是江城开朗的豌豆,一名拥有6年以上前端开发经验的工程师。我精通HTML、CSS、JavaScript
等基础前端技术,并深入掌握Vue、React、Uniapp、Flutter
等主流框架,能够高效解决各类前端开发问题。在我的技术栈中,除了常见的前端开发技术,我还擅长3D开发,熟练使用Three.js
进行3D图形绘制,并在虚拟现实与数字孪生技术上积累了丰富的经验,特别是在虚幻引擎开发方面,有着深入的理解和实践。
我一直认为技术的不断探索和实践是进步的源泉,近年来,我深入研究大数据算法的应用与发展,尤其在数据可视化和交互体验方面,取得了显著的成果。我也注重与团队的合作,能够有效地推动项目的进展和优化开发流程。现在,我担任全栈工程师,拥有CSDN博客专家认证及阿里云专家博主称号,希望通过分享我的技术心得与经验,帮助更多人提升自己的技术水平,成为更优秀的开发者。
技术qq交流群:906392632
大家好,我是小杨,一个做了6年前端的老司机。今天想和大家聊聊一个常见但头疼的问题:如何在不卡死页面的情况下渲染大量数据?
不知道你有没有遇到过这样的场景:后端一次性返回几万条数据,前端直接 forEach
+ appendChild
暴力渲染,结果页面直接卡死,用户疯狂投诉……(别问我怎么知道的 😅)
其实,解决这个问题并不难,关键在于分批渲染,而 requestAnimationFrame
(简称 rAF
)就是我们的最佳帮手!
1. 为什么直接渲染大数据会卡?
假设我们有 10,000 条数据,如果一次性全部塞进 DOM:
const data = Array(10000).fill("我是数据项");
const container = document.getElementById('list');
data.forEach(item => {
const div = document.createElement('div');
div.textContent = item;
container.appendChild(div);
});
问题来了:
- JS 执行阻塞:一次性创建 10,000 个 DOM 节点,JS 引擎会卡住主线程。
- 渲染延迟:浏览器需要处理大量 DOM 插入,导致页面冻结,用户无法交互。
- 内存占用高:大量 DOM 节点堆积,内存飙升,甚至可能崩溃。
结论: 不能一次性渲染所有数据!
2. 解决方案:分批渲染 + requestAnimationFrame
requestAnimationFrame
是浏览器提供的 API,它会在下一次重绘之前执行回调,通常是 16.6ms(60FPS) 执行一次,这样可以让渲染更流畅。
优化思路:
- 分批次渲染:每次只渲染一小部分数据(比如 50 条)。
- 利用
rAF
控制渲染时机,避免阻塞主线程。 - 滚动加载优化(可选):结合
IntersectionObserver
实现懒加载。
代码实现:
function renderLargeData(data, chunkSize = 50) {
const container = document.getElementById('list');
let index = 0;
function renderChunk() {
// 每次渲染 chunkSize 条数据
const chunkEnd = Math.min(index + chunkSize, data.length);
for (; index < chunkEnd; index++) {
const div = document.createElement('div');
div.textContent = 我是.data[index];
container.appendChild(div);
}
// 如果还有数据,继续渲染
if (index < data.length) {
requestAnimationFrame(renderChunk);
}
}
// 开始渲染
renderChunk();
}
// 测试
const bigData = Array(10000).fill("我是数据项");
renderLargeData(bigData);
优化效果:
✅ 页面不卡顿:每次只渲染少量 DOM,主线程不会被阻塞。
✅ 流畅渲染:rAF
确保在浏览器空闲时执行,不影响用户交互。
✅ 内存可控:不会一次性创建大量 DOM,减少内存压力。
3. 进阶优化:虚拟滚动(Virtual Scroll)
如果你的数据量真的超级大(比如 10W+),即使分批渲染也会占用大量 DOM,这时可以考虑 虚拟滚动(只渲染可视区域内的元素)。
核心思路:
- 只渲染可见区域的数据,滚动时动态替换内容。
- 使用
IntersectionObserver
或监听scroll
事件计算可视范围。
// 简单示例(完整实现会更复杂)
function renderVirtualScroll(data, container, itemHeight = 50) {
const viewportHeight = container.clientHeight;
const visibleItemCount = Math.ceil(viewportHeight / itemHeight);
let startIndex = 0;
function updateVisibleItems() {
const endIndex = startIndex + visibleItemCount;
const visibleData = data.slice(startIndex, endIndex);
container.innerHTML = '';
visibleData.forEach(item => {
const div = document.createElement('div');
div.style.height = `${itemHeight}px`;
div.textContent = 我是.item;
container.appendChild(div);
});
}
container.addEventListener('scroll', () => {
startIndex = Math.floor(container.scrollTop / itemHeight);
updateVisibleItems();
});
updateVisibleItems();
}
适用场景:
📌 超长列表(如聊天记录、日志数据)
📌 表格数据渲染
📌 无限滚动(Infinite Scroll)
4. 总结:如何选择优化方案?
方案 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
分批渲染(rAF ) | 数据量中等(1k~10k) | 实现简单,兼容性好 | DOM 节点仍较多 |
虚拟滚动 | 数据量超大(10k+) | 极致性能,内存占用低 | 实现较复杂 |
我的建议:
- 如果数据量 < 5000,直接渲染问题不大。
- 如果数据量 5000~10W,用
requestAnimationFrame
分批渲染。 - 如果数据量 > 10W,考虑虚拟滚动或后端分页。
5. 最后的小技巧
setTimeout
vsrAF
:rAF
比setTimeout
更适合动画/渲染,因为它与浏览器刷新率同步。- Web Worker:如果数据处理很耗时(如排序、过滤),可以丢给 Web Worker,避免阻塞 UI。
- 分页加载:如果可能,尽量让后端分页,减少前端压力。
结语
渲染大数据并不可怕,关键是要避免阻塞主线程。requestAnimationFrame
是一个强大的工具,合理使用能让你的页面丝滑流畅!