使用 `requestIdleCallback` 优化大批量 DOM 操作 —— 以加载 100 万条数据为例
在前端开发中,如果你尝试在短时间内往 DOM 中添加大量元素,比如一次性插入 100 万条数据,页面极有可能卡顿甚至直接崩溃。为了解决这一性能问题,我们可以借助浏览器提供的 `requestIdleCallback` 方法,利用浏览器空闲时间进行渐进式渲染。
本文将介绍 `requestIdleCallback` 的原理,并通过一个加载百万数据的例子,展示如何优化加载逻辑。我们会进行优化前后的代码对比,并最终输出完整的优化后代码。
🧠 `requestIdleCallback` 是什么?
`requestIdleCallback` 是浏览器提供的一种任务调度 API,它允许我们在主线程空闲时执行低优先级任务,从而避免阻塞用户交互。
基本用法:
requestIdleCallback(callback, { timeout });
- `callback(deadline)`:将在浏览器空闲时执行的回调函数。
- `deadline.timeRemaining()`:表示当前空闲周期内的剩余可用毫秒数。
- `timeout`(可选):指定在多少毫秒内必须执行任务,即使浏览器一直不空闲。
📌 应用场景:
- 批量数据渲染
- 懒加载组件
- 异步加载脚本
- 用户不感知的任务(如埋点、缓存处理等)
📉 性能问题分析(未优化)
假设我们要一次性渲染 100 万条数据,最直观的做法就是通过 `for` 循环和 DOM 操作一次性插入:
开始渲染
function startRender() {for (let i = 0; i < 1000000; i++) {const div = document.createElement('div');div.textContent = \`第\${i + 1}条数据\`;document.body.appendChild(div);}
}
⚠️ 问题:
- 页面会严重卡顿,甚至浏览器崩溃。
- 用户在等待过程中无法滚动、点击或交互。
- 非渐进式渲染,缺乏灵活性。
✅ 使用 `requestIdleCallback` 优化后的方案
我们将渲染任务分批处理,并在每次主线程空闲时执行下一批操作。这样浏览器可以在用户交互和渲染之间进行调度,避免“线程阻塞”。
优化思路:
- 每批最多渲染 100 条数据。
- 使用 `document.createDocumentFragment` 减少 DOM 操作开销。
- 使用 `requestIdleCallback` 判断是否还有空闲时间继续渲染。
🔍 优化前后对比图示(伪图)
未优化:一次性插入100w条
┌────┬────┬────┬────┬────┬────┐
│████████████ 卡顿 ███████████│
└────┴────┴────┴────┴────┴────┘
优化后:空闲时分批插入100条
┌█───█───█───█───█───█───█───█┐
│ 渐进渲染中… 用户可交互 │
└█───█───█───█───█───█───█───█┘
🧪 完整优化后代码示例
<script>const TOTAL_ITEMS = 1000000;let loadedCount = 0;// 创建文档片段优化DOM操作function createBatch() {const fragment = document.createDocumentFragment();const batchSize = Math.min(100, TOTAL_ITEMS - loadedCount);for(let i=0; i<batchSize; i++) {const div = document.createElement('div');div.textContent = \`第\${++loadedCount}条数据\`;fragment.appendChild(div);}document.body.appendChild(fragment);return loadedCount < TOTAL_ITEMS;}// 使用requestIdleCallback调度function scheduleLoading() {requestIdleCallback((deadline) => {const startTime = performance.now();let hasMore = true;while (deadline.timeRemaining() > 0 && hasMore &&performance.now() - startTime < 50 // 防止单次执行过久) {hasMore = createBatch();}if(hasMore) {scheduleLoading();} else {console.log('所有数据加载完成');}}, { timeout: 1000 });}function startLoading() {loadedCount = 0;scheduleLoading();}
</script>
📌 总结
- `requestIdleCallback` 提供了一种非常适合执行非关键任务的机制。
- 对于大批量 DOM 操作,我们应避免一次性操作 DOM,采用分批 + 空闲加载策略。
- 在用户体验和页面性能之间找到平衡,是现代前端优化的核心。
✨ 如果你觉得这篇文章对你有帮助,不妨点个 免费的赞 或留言交流哦!