回顾流程
1,从任务队列中取出一个宏任务并执行。
2,检查微任务队列,执行并清空微任务队列,如果在微任务的执行中又加入了新的微任务,也会在这一步一起执行。
3,进入更新渲染阶段,判断是否需要渲染,这里有一个 rendering opportunity 的概念,也就是说不一定每一轮 event loop 都会对应一次浏览 器渲染,要根据屏幕刷新率、页面性能、页面是否在后台运行来共同决定,通常来说这个渲染间隔是固定的。(所以多个 task 很可能在一次渲染之间执行)
- 浏览器会尽可能的保持帧率稳定,例如页面性能无法维持 60fps(每 16.66ms 渲染一次)的话,那么浏览器就会选择 30fps 的更新速率,而不是偶尔丢帧。
- 如果浏览器上下文不可见,那么页面会降低到 4fps 左右甚至更低。
- 如果满足以下条件,也会跳过渲染:
跳过渲染条件:
1,浏览器判断更新渲染不会带来视觉上的改变。
2,map of animation frame callbacks 为空,也就是帧动画回调为空,可以通过 requestAnimationFrame 来请求帧动画。
如果上述的判断决定本轮不需要渲染,那么下面的几步也不会继续运行:
This step enables the user agent to prevent the steps below from running for other reasons, for example, to ensure certain tasks are executed immediately after each other, with only microtask checkpoints interleaved (and without, e.g., animation frame callbacks interleaved). Concretely, a user agent might wish to coalesce timer callbacks together, with no intermediate rendering updates. 有时候浏览器希望两次「定时器任务」是合并的,他们之间只会穿插着 microTask的执行,而不会穿插屏幕渲染相关的流程(比如requestAnimationFrame,下面会写一个例子)。
对于需要渲染的文档,如果窗口的大小发生了变化,执行监听的 resize 方法。
对于需要渲染的文档,如果页面发生了滚动,执行 scroll 方法。
对于需要渲染的文档,执行帧动画回调,也就是 requestAnimationFrame 的回调。(后文会详解)
对于需要渲染的文档, 执行 IntersectionObserver 的回调。
对于需要渲染的文档,重新渲染绘制用户界面。
判断 task队列和microTask队列是否都为空,如果是的话,则进行 Idle 空闲周期的算法,判断是否要执行 requestIdleCallback 的回调函数。(后文会详解)
对于resize 和 scroll来说,并不是到了这一步才去执行滚动和缩放,那岂不是要延迟很多?浏览器当然会立刻帮你滚动视图,根据CSSOM 规范所讲,浏览器会保存一个 pending scroll event targets,等到事件循环中的 scroll这一步,去派发一个事件到对应的目标上,驱动它去执行监听的回调函数而已。resize也是同理。
可以在这个流程中仔细看一下「宏任务」、「微任务」、「渲染」之间的关系。
多任务队列
task 队列并不是我们想象中的那样只有一个,根据规范里的描述:
An event loop has one or more task queues. For example, a user agent could have one task queue for mouse and key events (to which the user interaction task source is associated), and another to which all other task sources are associated. Then, using the freedom granted in the initial step of the event loop processing model, it could give keyboard and mouse events preference over other tasks three-quarters of the time, keeping the interface responsive but not starving other task queues. Note that in this setup, the processing model still enforces that the user agent would never process events from any one task source out of order.
事件循环中可能会有一个或多个任务队列,这些队列分别为了处理:
鼠标和键盘事件
其他的一些 Task
浏览器会在保持任务顺序的前提下,可能分配四分之三的优先权给鼠标和键盘事件,保证用户的输入得到最高优先级的响应,而剩下的优先级交给其他 Task,并且保证不会“饿死”它们。
这个规范也导致 Vue 2.0.0-rc.7 这个版本 nextTick 采用了从微任务 MutationObserver 更换成宏任务 postMessage 而导致了一个 Issue。
目前由于一些“未知”的原因,jsfiddle 的案例打不开了。简单描述一下就是采用了 task 实现的 nextTick,在用户持续滚动的情况下 nextTick 任务被延后了很久才去执行,导致动画跟不上滚动了。
迫于无奈,尤大还是改回了 microTask 去实现 nextTick,当然目前来说 promise.then 微任务已经比较稳定了,并且 Chrome 也已经实现了 queueMicroTask 这个官方 API。不久的未来,我们想要调用微任务队列的话,也可以节省掉实例化 Promise 在开销了。
从这个 Issue 的例子中我们可以看出,稍微去深入了解一下规范还是比较有好处的,以免在遇到这种比较复杂的 Bug 的时候一脸懵逼。
微任务,宏任务https://zhuanlan.zhihu.com/p/168755153
https://www.yuque.com/suihangadam/powcpt/mhckt6