requestIdleCallback 是一个 Web API,它允许开发者在浏览器空闲时执行某些任务。这个 API 设计目的是为了让开发者能够在浏览器渲染帧之间,或其他非关键时刻执行一些低优先级的工作,从而避免阻塞主线程,影响用户的交互体验。
requestIdleCallback 是由 Chrome 首先引入的,并且已经被其他浏览器(如 Firefox、Safari)部分支持。它是在 Web Performance API 的一部分,并且专门为 “空闲时间” 提供的 API。
空闲时间通常是指,浏览器没有被用户交互(如点击、滚动等)或更新渲染(如页面动画、DOM更新等)占用的时间。
与 setTimeout 和 setInterval 等传统的定时器 API 不同,浏览器将 requestIdleCallback 调度为一个任务队列中的回调函数,且该回调函数将在浏览器空闲时执行,这样可以最大化渲染的流畅度,避免占用主线程资源。
在了解 requestIdleCallback 之前,先理解浏览器的事件循环机制。
事件循环是浏览器处理 JavaScript 代码、用户输入、渲染和其他任务的核心机制。它的基本流程如下:
下面先简单提一下具体流程。
当调用 requestIdleCallback 时,流程如下:
requestIdleCallback(myCallback);
function myCallback(deadline) {
while (deadline.timeRemaining() > 0) {
// 执行一些非紧急的任务
console.log('执行任务...');
}
// 如果还有任务未完成,可以在下次空闲时继续处理
if (/* 还有更多任务 */) {
requestIdleCallback(myCallback);
}
}
requestIdleCallback(myCallback, { timeout: 1000 });
requestIdleCallback 用法与传统的 setTimeout 类似,但它有一些特殊的处理机制。最简单的使用方式如下:
参数说明:
Tip:requestIdleCallback 不会强制执行任务,它会尽量在浏览器空闲时执行回调,但如果浏览器忙于其他任务,回调可能会延迟执行。
浏览器的渲染引擎通常会占用主线程来处理用户交互和渲染工作,这导致开发者有时难以在这些繁忙时刻执行一些额外的任务(例如后台数据加载、日志记录、非关键的 DOM 操作等)。
requestIdleCallback 主要解决问题包括:
1、优点
2、缺点
特性 | requestIdleCallback | setTimeout | setInterval |
---|---|---|---|
执行时机 | 在浏览器空闲时执行(优先级低的任务) |
在指定的时间后执行,可能会在高优先级任务期间被中断 | 在指定时间间隔周期性执行,可能会在高优先级任务期间被中断 |
适用场景 | 空闲时间的低优先级任务(如懒加载、后台同步、日志记录等) | 延迟执行某个任务,通常用于定时操作(如动画、轮询) | 定时周期性执行任务(如轮询、计时器) |
优先级 | 低优先级任务,且不会阻塞页面渲染或用户交互 | 可以与浏览器渲染任务争夺主线程资源,影响性能 | 与 setTimeout 类似,可能影响页面渲染性能 |
支持性 | 目前主要在 Chrome 支持,其他浏览器支持较差 | 广泛支持(几乎所有浏览器) | 广泛支持(几乎所有浏览器) |
可控性 | 不能精确控制执行时机,只能在空闲时执行 | 可以精确控制执行时机 | 可以精确控制执行时机,周期性执行任务 |
典型例子:假设有一个网页,里面有很多图片,希望用户滚动到这些图片时再加载它们。可以使用 requestIdleCallback 来在浏览器空闲时加载这些图片。
const images = document.querySelectorAll('img[data-src]');
function loadImages() {
images.forEach((img) => {
if (img.getBoundingClientRect().top < window.innerHeight) {
img.src = img.dataset.src; // 将 data-src 的值赋给 src
img.removeAttribute('data-src'); // 移除 data-src 属性
}
});
}
function idleCallback(deadline) {
while (deadline.timeRemaining() > 0 && images.length > 0) {
loadImages();
}
}
// 每次空闲时调用
requestIdleCallback(idleCallback);
假设有一个需要处理大量数据的任务,比如渲染一个长列表。可以将这个任务分成多个小块,在浏览器空闲时逐步处理。
const data = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
let index = 0;
function renderItems(deadline) {
while (deadline.timeRemaining() > 0 && index < data.length) {
const item = document.createElement('div');
item.textContent = data[index++];
document.body.appendChild(item);
}
if (index < data.length) {
requestIdleCallback(renderItems); // 继续处理剩余的项
}
}
// 开始处理
requestIdleCallback(renderItems);