事件循环规范

EventLoop Specification

规范链接

  1. 找到当前taskQueue对应的task queue的处理机制,处理其中的task。如果没有task queue的处理机制,跳转到microtask queue(微任务队列)步骤中。

microtask queue 不是 task queue,所以不会在这一步中被当做task处理。

taskQueue是内存中的数据结构,task queue是指不同类型的task队列。

  1. 将最老的任务作为taskQueue中第一个可执行的任务,然后将其从taskQueue中移除。

  2. 处理当前可执行的任务并将EventLoop中当前处理的任务作为最老的任务。

  3. 将当前时间设置为taskStartTime

  4. 执行最老的任务的steps

  5. 将EventLoop的当前执行的task设置为null。

  6. 执行microtask checkpoint。这个步骤其实包含了多个子步骤,只要microtask queue不空,这一步会一直从microtask queue中取出microtask,执行之。如果microtask执行过程中又添加了microtask,那么仍然会执行新添加的microtask。

  7. 设置hasARenderingOpportunity为false。

  8. 记录当前时间赋值给now。

  9. 报告任务的执行时间:

    10.1. 将顶级的浏览上下文设置为空set。(浏览器中设置为window,nodejs中设置为global)

    10.2. 将最老的任务的上下文环境设置为顶级浏览上下文环境。(这的任务是指task,不是microtask,microtask会依据词法环境设置上级上下文。)

    10.3. 通过参数taskStartTimenowtop-level browsing contextsoldestTask上报任务执行时间。

  10. 更新渲染:

    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的渲染和用户接口,也将上下文更新到当前的状态。

  11. 如果下面条件成立:

    • 当前事件是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在重绘之前执行。

总结

本文在理解规范的基础上进行翻译,有不当忘指出。

参考文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值