You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
嵌套递归调用微任务 API 会导致死循环,JS 引擎需要执行完全部微任务才会让出控制权,因此不适用于任务调度
requestAnimationFrame、requestIdleCallback、setTimeout、MessageChannel 等 API 嵌套递归调用不会导致死循环,JS 引擎每执行完一次回调都会让出控制权,适用于任务调度。我们需要综合考虑这几个 API 调用间隔、执行时机等因素选择合适的 API
学习目标
了解屏幕刷新率,下面这些 API 的基础用法及执行时机。从浏览器 Performance 面板中看每一帧的执行时间以及工作。探索哪些 API 适合用来调度任务
屏幕刷新率
以下面的例子为例:
在浏览器控制台的 performance 中查看上例的运行结果,如下图所示:
从图中可以看出每一帧的执行时间都是 16.7ms,在这一帧内,浏览器执行 raf,计算样式,布局,重绘,requestIdleCallback、定时器,放大每一帧可以看到:
requestAnimationFrame
requestAnimationFrame 在每一帧绘制之前执行,嵌套(递归)调用 requestAnimationFrame 并不会导致页面死循环从而崩溃。每执行完一次 raf 回调,js 引擎都会将控制权交还给浏览器,等到下一帧时再执行。
上面的例子中使用 while 循环模拟耗时 2 毫秒的任务,观察浏览器页面发现动画很流畅,Performance 查看每一帧的执行情况如下:
如果将 while 循环改成 100 毫秒,页面动画明显的卡顿,Performance 查看会提示一堆长任务
requestIdleCallback
requestIdleCallback 在每一帧剩余时间执行。
本例中使用
deadline.timeRemaining() > 0 || deadline.didTimeout
判断如果当前帧中还有剩余时间,则继续 while 循环Performance 查看如下,几乎用满了一帧的时间,极致压榨 😁
setTimeout
setTimeout 是一个宏任务,用于启动一个定时器,当然时间间隔并不一定准确。在本例中我将间隔设置为 0 毫秒
Performance 查看如下,可以发现,即使我将时间间隔设置为 0 毫秒,两次 setTimeout 之间的间隔差不多是 4 毫秒(如图中红线所示)。可以看出 setTimeout 会有至少 4 毫秒的延迟
MessageChannel
和 setTimeout 一样,MessageChannel 回调也是一个宏任务,具体用法如下:
Performance 查看如下:
放大每一帧可以看到,一帧内,MessageChannel 回调的调用频次超高
从图中可以看出,相比于 setTimeout,MessageChannel 有以下特点:
微任务
微任务是在当前主线程执行完成后立即执行的,浏览器会在页面绘制前清空微任务队列,嵌套调用微任务会导致死循环。这里我会介绍两个微任务相关的 API
Promise
在这个例子中,我使用 count 来控制 promise 嵌套的次数,防止死循环
这里,我在 requestAnimationFrame 调用 mymicrotask,mymicrotask 中会调用 Promise 启用一个微任务,在 Promise then 中又会嵌套调用 mymicrotask 递归的调研 Promise。从图中可以看到,在本次页面更新前执行完全部的微任务
如果像下面这样嵌套调用,页面直接卡死,和死循环效果一样
MutationObserver
和 Promise 一样,为了防止死循环,我使用 count 控制,在一次 raf 中只调用 2000 次 mymicrotask
当然,如果取消 count 的限制,页面直接卡死,死循环了。
小结
从上面的例子中可以看出
相关 issue
实际上,React 团队也针对这些 API 进行尝试,下面是相关 issue
The text was updated successfully, but these errors were encountered: