EventLoop Specification
- 找到当前taskQueue对应的task queue的处理机制,处理其中的task。如果没有task queue的处理机制,跳转到microtask queue(微任务队列)步骤中。
microtask queue 不是 task queue,所以不会在这一步中被当做task处理。
taskQueue是内存中的数据结构,task queue是指不同类型的task队列。
-
将最老的任务作为taskQueue中第一个可执行的任务,然后将其从taskQueue中移除。
-
处理当前可执行的任务并将EventLoop中当前处理的任务作为最老的任务。
-
将当前时间设置为
taskStartTime
。 -
执行最老的任务的steps。
-
将EventLoop的当前执行的task设置为null。
-
执行microtask checkpoint。这个步骤其实包含了多个子步骤,只要microtask queue不空,这一步会一直从microtask queue中取出microtask,执行之。如果microtask执行过程中又添加了microtask,那么仍然会执行新添加的microtask。
-
设置hasARenderingOpportunity为false。
-
记录当前时间赋值给now。
-
报告任务的执行时间:
10.1. 将顶级的浏览上下文设置为空set。(浏览器中设置为window,nodejs中设置为global)
10.2. 将最老的任务的上下文环境设置为顶级浏览上下文环境。(这的任务是指task,不是microtask,microtask会依据词法环境设置上级上下文。)
10.3. 通过参数
taskStartTime
,now
,top-level browsing contexts
和oldestTask
上报任务执行时间。 -
更新渲染:
11.1. 将当前EventLoop涉及到的Document Object赋值给docs(这里就是DOM树),docs可以采用任意数据结构储存(这一步就是为了正确地用docs数据结构储存Document节点对象。下面的步骤中处理每个Document对象的顺序是在docs中查找的顺序),但是要遵守以下约定:
这些约定是为了生成一个合理的DOM树数据结构。 Document B 对象的浏览上下文的容器对象是Document A的话,B必须在列在A之后。 如果两个Document对象A,B,他们的上下文互相是对方的子上下文(A去访问B中的内容,B去访问A中的内容),他们的容器是Document C对象,那么A和B的顺序必须与他们在C的节点树中各自上下文中包含阴影的树的顺序相同(以A和B为根节点,根据A树和B树的节点确定A和B的位置)。
11.2. 将docs中所有不具有渲染机会的节点从docs中移除。如果客户端能够将浏览上下文的内容呈现给用户,就表明该上下文有渲染机会。是否能呈现给用户也取决于硬件的刷新率和其他参数的(例如将Document对象的可视属性设置为
visible
)。本规范不强制要求任何特定模型来选择渲染机会。 如果浏览器达到 60Hz 的刷新率,那么渲染机会大约 16.7 毫秒发生一次。 如果浏览器发现浏览上下文无法维持此速率(EventLoop的每轮速度低于刷新率),它可能会降低该浏览上下文的每秒 30 次渲染机会,而不是偶尔丢帧。 类似地,如果浏览上下文不可见,用户代理可能会决定将该页面降低到每秒 4 次渲染机会,甚至更少。(performance面板mainThread中的一个task执行时间是动态的,浏览器会根据这个执行时间来调整渲染机会,即使浏览器给了渲染机会,但是是否会渲染也会取决于docs是否有变化) 由于规范没有做约定,所以浏览器在 render 策略上有充分的自主性。既有可能出现每一轮 eventloop 后都 render 的现象,也有可能出现几十轮 eventloop 都不 render 的情况。
11.3. 如果docs发生了改变且不为空,将
hasARenderingOpportunity
设置为true,并且将EventLoop的上一次渲染结束的时间设置为taskStartTime。11.4. 不必要的渲染:
1、客户端认为更新 Document 浏览上下文的渲染不会有效果。 2、动画帧回调的文档映射是空的。
11.5. 这一步是交给客户端发挥的。浏览器根据自己的经验将那些因为某些原因不需要渲染的Document对象从docs中移除。某些原因是什么愿意呢?这个根据不同厂家自己的经验去判定。
11.6. 对于每个在docs中处于活跃状态的Document对象,如果该Document对象的上下文是当前浏览的顶级上下文,自动将焦点刷新到该对象上。
11.7. 对于每个在docs中处于活跃状态的Document对象,运行resize步骤,将now作为时间戳传入resize。
11.8. 对于每个在docs中处于活跃状态的Document对象,运行scroll步骤,将now作为时间戳传入scroll。
11.9. 对于每个在docs中处于活跃状态的Document对象,评估媒体查询并报告该Document对象的更改,将now作为时间戳传入本行为。
11.10. 对于每个在docs中处于活跃状态的Document对象,更新动画并触发该对象中的事件,将now作为时间戳传入本行为。
11.11. 对于每个在docs中处于活跃状态的Document对象,运行该文档的fulscreen步骤,将now作为时间戳传入本行为。
11.12. 对于每个在docs中处于活跃状态的Document对象,如果客户端发现在后备储存中没有和
CanvasRenderingContext2D
或者OffscreenCanvasRenderingContext2D
相关联的上下文,然后执行下面的步骤:这些步骤就是为了初始化canvas上下文,使画布正确展示出来。 1、将canvas的值设置为上下文canvas的特性值,如果上下文是`CanvasRenderingContext2D`,否则设置为上下文关联的`OffscreenCanvas`对象。 2、将context lost设置为true。 3、将渲染的上下文设置为给定上下文的默认状态。 4、将shouldRestore设置为在canvas触发的contextlost事件的结果,将cancleable属性设置为true。 5、将shouldRestore设置为false,然后终止这一相关步骤。 6、使用上下文的特性值初始化canvas的上下文,并将其作为后备储存,如果这一步骤失败了,就放弃本步骤。 7、将context lost设置为false。 8、触发contextrestored事件。
11.13. 对于每个在docs中处于活跃状态的Document对象,执行当前Document的animation frame的回调函数,将now作为时间戳传入回调函数。
11.14. 对于每个在docs中处于活跃状态的Document对象,为当前Document对象执行插入observations的步骤,将now作为时间戳传入本行为。
11.15. 调用标记paint事件算法为当前Document对象。
11.16. 对于每个在docs中处于活跃状态的Document对象,更新当前Document的渲染和用户接口,也将上下文更新到当前的状态。
-
如果下面条件成立:
- 当前事件是window事件
- task queue中没有活跃的事件处理函数
- 微任务队列为空
- hasARenderingOpportunity为false
12.1. 将
computeDeadline
设置为以下步骤:1、将deadline设置为这个事件循环的最后一个空闲周期开始的时间加上50ms。 50ms的预留是为了响应人类感知的阈值。 2、将hasPendingRenders设置为false。 3、对于同一窗口的事件循环中的windowInSameLoop属性进行以下步骤: 如果windowInSameLoop的回调函数不为空,或者客户端确定windowInSameLoop事件中处于活跃状态的渲染,将hasPendingRenders设置为true。 将timerCallbackEstimates设置为windowInSameLoop对应的计时器的值。 对于timerCallbackEstimates中的timeoutDeadline,如果timeoutDeadline小于deadline,将deadline设置为timeoutDeadline。 4、如果hasPendingRenders为true: 将nextRenderDeadline设置为当前事件循环的最后一次渲染的时间加上(1000/刷新率),刷新率取决于硬件。 如果nextRenderDeadline小于deadline,然后返回nextRenderDeadline。 5、返回deadline。
12.2. 对于同一窗口的事件循环,为当前窗口使用computeDeadline执行启动空闲期的算法。
注意:
- 不是每轮EventLoop都会render UI,如果界面刷新率为60HZ,setInterva()的执行事件的时间间隔设置为1ms是没有意义的。
- 不需要在resize、scroll中使用requestAnimationFrame来节流(因为resize、scroll的节流效果和requestAnimationFrame节流效果相同)。但是可以在resize、scroll使用throttle来节流。
- requestAnimationFrame在重绘之前执行。
总结
本文在理解规范的基础上进行翻译,有不当忘指出。