浏览器是单线程的,这意味着 JavaScript 的执行、页面布局和绘制都在同一个主线程上进行。
任何一个耗时过长的 JS 任务,都会阻塞后续的渲染工作,导致页面卡顿、掉帧,甚至无响应。
setTimeout(fn, 0)
被用来处理一些非紧急但又希望尽快执行的任务,我们期望它能将一个任务推迟到未来的某个时刻执行,从而让主线程得以喘息:
setTimeout(() => {
// 在未来的某个时间点执行我
console.log("Hello, after 1000ms!");
}, 1000);
**setTimeout 的困境:不守时 **
然而, setTimeout 很快暴露了它的致命缺陷:它并不关心浏览器正在做什么。
- 延迟不准:到点不一定执行,主线程忙就得排队
- 时机不佳:当浏览器正渲染时插队,导致掉帧卡顿
requestAnimationFrame
为了解决动画卡顿的问题,浏览器厂商们意识到,需要一个与“视觉”同步的调度API。于是, requestAnimationFrame (rAF)
应运而生。

rAF 的设计目标非常明确:在浏览器下一次重绘(repaint)之前执行回调函数。
**rAF 的局限 **
rAF 完美地解决了高优先级视觉任务的调度问题,但如果我们将一个非紧急、耗时较长的后台任务(比如发送日志、数据预处理)放进 rAF
,就又回到了老问题:它仍然会阻塞关键的渲染路径,导致卡顿。
requestIdleCallback
requestIdleCallback (rIC) 的出现,标志着浏览器调度进入了一个“智能”和“协作”的时代。
rIC 的核心理念是:在浏览器主线程的“空闲时期”执行低优先级的回调函数。
requestIdleCallback((deadline) => {
// 只有在浏览器有空的时候,我才会被执行
console.log('Browser is idle, let\'s do some work!');
});
**rIC 的智慧:见缝插针 **
rIC调度器会在一帧的末尾(当布局、绘制等高优任务完成后)检查是否有剩余时间,空闲时才会执行rIC的回调- 协作式调度与
deadline:其回调函数会接收一个deadline对象,其中最重要的属性是timeRemaining()方法,返回空闲时间
let tasks = [task1, task2, task3, task4, ...];
function processTasks(deadline) {
// 只要还有空闲时间,并且还有任务,就继续处理
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
let task = tasks.shift();
execute(task);
}
// 如果任务还没做完,就预约下一次空闲时间继续
if (tasks.length > 0) {
requestIdleCallback(processTasks);
}
}
requestIdleCallback(processTasks);
这个模式将一个可能阻塞线程的大任务,分解成多个小任务块。在每个空闲时期,只处理一小部分,并通过 deadline.timeRemaining()
判断是否应该“让出”主线程,将剩余的工作推迟到下一个空闲周期。
代码不再是霸道地占用线程,而是与浏览器友好协商,共同维护用户体验。