告别 setTimeout,前端调度进入智能时代
在前端开发中,浏览器是单线程的。这意味着 JavaScript 的执行、页面布局和渲染都在同一个主线程上进行。任何一个耗时的 JS 任务,都可能阻塞渲染,导致页面卡顿、掉帧甚至无响应。
过去,我们常用 setTimeout(fn, 0)
或类似手段,将任务推迟执行,以给主线程“喘息”的机会:
setTimeout(() => {
console.log("Hello, after 1000ms!");
}, 1000);
然而,setTimeout
并非总是可靠的调度器。
setTimeout 的困境:不守时
- 延迟不准:浏览器主线程忙时,回调会排队,无法准时执行
- 时机不佳:可能在页面渲染关键阶段插队,导致掉帧或卡顿
换句话说,setTimeout
并没有考虑浏览器的渲染节奏,它只是一个简单的延迟队列。
requestAnimationFrame (rAF):与视觉同步
为了解决动画流畅性问题,浏览器提供了 requestAnimationFrame
(rAF):
requestAnimationFrame(() => {
console.log('下一次重绘前执行动画逻辑');
});
特点
- 在浏览器 下一次重绘前 执行回调
- 非常适合高优先级视觉任务,如动画更新
局限
rAF 完美解决了动画调度,但如果用于低优先级、耗时的后台任务(如日志上报、数据预处理),仍会阻塞渲染路径,造成性能问题。
requestIdleCallback (rIC):协作式调度
为了解决低优先级任务阻塞问题,现代浏览器提供了 requestIdleCallback
(rIC):
requestIdleCallback((deadline) => {
console.log('浏览器空闲了,我可以做一些任务');
});
核心理念
- 在主线程空闲时执行任务
- 不抢占关键渲染,保证页面流畅
deadline 对象与时间管理
rIC 回调会接收一个 deadline
对象,其中 timeRemaining()
方法返回当前空闲时间(毫秒):
let tasks = [task1, task2, task3];
function processTasks(deadline) {
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
let task = tasks.shift();
execute(task);
}
if (tasks.length > 0) {
requestIdleCallback(processTasks);
}
}
requestIdleCallback(processTasks);
✅ 优点:
- 任务分块处理:大任务拆分成多个小任务,每次只占用空闲时间
- 协作式调度:通过
timeRemaining()
判断是否应让出主线程 - 性能友好:保持用户界面流畅,无阻塞
总结
- setTimeout:简单延迟,但不考虑渲染时机,可能掉帧
- requestAnimationFrame:完美处理动画和视觉任务
- requestIdleCallback:智能调度低优先级任务,友好协作主线程
前端调度已经进入了 “智能协作时代”。通过 rAF 与 rIC 的组合,开发者可以精确控制高优先级和低优先级任务,让页面性能与用户体验双赢。