用一行代码替换setTimeout

浏览器是单线程的,这意味着JavaScript执行、页面布局和绘制都在同一个主线程上完成。

任何耗时过长的JS任务都会阻塞后续的渲染工作,导致页面卡顿、掉帧,甚至完全失去响应。

setTimeout(fn, 0) 用于执行我们希望尽快运行但不紧急的任务,我们期望它能将任务推迟到未来的某个时间点,让主线程喘口气:

setTimeout(() => {
  // 在未来的某个时间点执行这个函数
  console.log("Hello, after 10000ms!");
}, 10000);

setTimeout的困境:不守时

不过,它很快暴露出一个致命缺陷:它不在乎浏览器正在做什么。

setTimeout
  • 延迟不准确:它可能不会在预定时间点执行,当主线程繁忙时任务会被排队
  • 时机不佳:在浏览器渲染时插队,导致掉帧和卡顿

requestAnimationFrame

为了解决动画卡顿问题,浏览器厂商意识到需要一个与”视觉”同步的调度API。于是,(rAF)应运而生。

requestAnimationFrame
function animate() {
  // 在这里执行一些 DOM 样式操作,列如改变元素的位置、透明度等
  // ...

  // 再次调用 requestAnimationFrame,让动画持续进行
  requestAnimationFrame(animate);
}

// 启动动画循环
requestAnimationFrame(animate);

rAF 的设计目标超级明确:在浏览器下一次重绘之前执行回调函数。

rAF的局限性

rAF 它完美解决了高优先级视觉任务的调度问题,但如果我们把一个不紧急、耗时的后台任务(列如发送日志、数据预处理)放进其中,就又回到了老问题:它依旧会阻塞关键渲染路径,导致卡顿。

requestIdleCallback

requestIdleCallback (rIC) 标志着浏览器调度进入了”智能化”和”协作化”时代。

rIC 的核心思想是在浏览器主线程的”空闲期”执行低优先级回调函数。

requestIdleCallback((deadline) => {
// This will only be executed when the browser is idle
console.log('浏览器闲暇时间可以做一些工作了');
});

rIC的智慧:见缝插针

  1. rIC 调度器会在每帧结束时检查剩余时间(此时布局、绘制等高质量任务已完成),只有当主线程空闲时才会执行rIC的回调
  2. 协作式调度与deadline:它的回调函数接收一个deadline对象,其中最重大的是timeRemaining()方法,用于返回可用的空闲时间
requestIdleCallback((deadline) => {
  // 只有当浏览器空闲时才会执行这里的函数
  console.log('Browser is idle, let's do some work!');
});

这种模式将可能阻塞线程的大任务分解为较小的任务块。在每个空闲期,只处理一小部分任务,剩余工作通过判断主线程是否应该”让步”,推迟到下一个空闲周期处理。

deadline.timeRemaining()

代码不是霸占线程,而是与浏览器友善协商,共同维护用户体验。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
吃一个饭的头像 - 鹿快
评论 抢沙发

请登录后发表评论

    暂无评论内容