【译】Firefox 52 中对于 setTimeout() 的改动

火狐浏览器52版本的 setTimeout 的改动

编写于 2017.3.13 标签: dom, mozilla, timer

上一周火狐浏览器52版本发布了,其中包括对 setTimeout() 以及 setInterval() 的一些改变。特别是我们改变了怎样去调度以及执行计时器回调以减少页面崩溃的风险。

在开始前,先看一下这个简单的demo(你可能不会想要打开这个网页)

Demo Site

当你点击 'Start' 按钮的时候,浏览器就开始被网站上触发的 setTimeout() 计时器给泛滥了。每一个回调都会触发一个 setTimeout() 两次。这将会导致计时器的指数爆炸。点击 'Stop' 将会停止触发 setTimeout()

页面上有个GIF动画以便你能直观的看见是否有页面崩溃的产生。(这是一个非常棒的想法,我从 Nolan Lawson's IDB performance post 那儿偷来的。

一般的来说,浏览器将会丢帧当这样的事情发生的时候(计时器指数爆炸),并且GIF也会停止动画。比如,这个是 Firefox 45 ESR版本运行上面的Demo的视频:

在Firefox 45 ESR版本中

在Firefox 52版本中,我们做了一些改变以便让浏览器在遇到这种情况是能够最大化的存活(不崩溃)。下面这个video就展示了效果。经过一段展示的停顿,这个GIF动画继续还算顺利的渲染尽管有计时器洪水(我个人理解是计时器爆炸)。

在Firefox 52版本中

它是怎么工作的?

Firefox 实现这个是通过实施 yielding 在计时器的回调之间。 当一个计时器的回调开始执行的时候,我们允许任何其他的非计时器事件在下一个计时器回调开始运行之前完成。

比如,考虑这样一种情况,当我们有一批的计时器回调,希望在同步刷新的同时运行。这是一个比赛,事件将首先运行,然而,刷新通常被认为更重要,因为如果它被延迟,则站点的每秒帧数将下降。

考虑到这一点,思考一下调度事件的“最佳”情况和“最差”情况:

the best

在最好的情况下,刷新首先运行并且不会被延迟。在最坏的情况下,刷新被延迟直到所有的计时器回调都被执行完成。在极端的情况下,像上面的演示,这个延迟可以是很长。

在计时器回调之间的Yielding会改变这种情况,所有最坏的情况将会被这个替代:

Yielding

现在,刷新将最多被一个回调延迟。

事实上我们并没有真正的重置所有事件在事件列表中。也许一个更好的方式来认为它是计时器存储在一个单独的队列,在任何时候只允许在主事件队列上调度单个计时器

other thinks

因此当 “callback1”完成后 “callback2” 才会被加入到主事件队列的末尾,这允许接下来执行刷新事件。

这是节流吗?

不是。“计时器节流”意味着向每个计时器引入一定量的延迟。例如,如果在后台选项卡中调用 setTimeout(func, 5) ,大多数浏览器会将计时器回调延迟至少1秒钟。

Yielding 的不同之处在于,如果主线程空闲,它允许计时器以全速运行。如果主线程繁忙,Yielding只会导致计时器延迟。(当然,如果主线程忙,那么计时器总是有被延迟的风险。)

也就是说,如果我们检测到计时器队列正在备份,我们开始节流计时器。这个负载有助于避免在脚本生成更多setTimeout() 调用的时候耗尽内存。这种负载被调整到只在极端情况下触发,大多数网站不应该体验它。

这是优先级吗?

不是。计时器的Yielding与使用优先级队列和标记计时器回调优先级不同。在严格的优先级排序方案中,低优先级事件将可能从不运行。这不是这种情况。

在我们的计时器的Yielding方法中,下一个计时器回调以与所有其他事件相同的优先级运行。它可以在其他工作之前执行。它也保证在某个点执行。

有什么收获

虽然我们的一般方法是在计时器之间Yielding,但我们的最终解决方案实际上并不这样做。我们实际上允许有限数量的计时器回调运行不经过使用yielding。我们这样做是为了减轻对使用计时器但饱和主线程的网站的影响。

比如,考虑到这样的网站:

  1. 通过大量的计时器回调运行动画。
  2. 动画渲染使主线程变得饱和。

在这种情况下计时器回调将会由渲染的速率来节流。当浏览器不能以60FPS渲染时,那么在每个刷新驱动程序事件之间最多只能获得一个计时器回调。

busy

对于“闭环”动画,这不是一个问题,你可以衡量运行的时间和调整你的更改以匹配。但是,他可以显著增加“开环”动画的整体动画时间。

比如,看看这个动画演示网站
“Open Loop” Animation Demo

这里,这个网站预先计算所有的动画步骤,并为每一个计划一个单独的 setTimeout() 。每一个计时器回调只是修改其步骤的DOM。而不是测量动画是否落后。

这个演示网站将导致几乎每个现代浏览器下降到零帧每秒。然而,总动画将很快运行。
普通现代浏览器

在Firefox 52 ,由于我们的Yielding使许多的计时器被延迟。这使得浏览器运行在30FPS,但是因此动画需要更长的时间来完成:
Firefox 52

这是一个极端的情况,我们不认为反映了大多数网站的典型行为有许多的方法来实现这个动画,而不需要成千上百的同时计时器。网站很可能使用这些备用方法来避免触发由此技术引起的差的FPS性能。

话虽如此。我们仍然想尽可能的避免打破现有的网站,这就是为什么我们不是在每个计时器回调之后执行严格的yield。我们希望通过允许几个计时器回调运行而不产生损失,我们可以减轻对这些类型的工作负载的影响,同时仍然提高一般的站点的性能。

下一步是什么

这些 setTimeout() 更改刚刚更新到Firefox 52.我们将会持续的寻找任何不被察觉的兼容性问题。到目前为止,我们只有一个单一的错误报告在四个月内,因为它降落在夜间。

如果你认为你的网站在Firefox中有因为这个改动而导致的问题,请报告错误并且将我添加到CC列表。

除了大规模的问题,我们计划继续改进这种方法。我们可能会改变对“允许出现的计时器”的限制,使用时间预算方法而不是固定数字。另外,Quantum DOM项目将对一般的事件队列调度进行更多的改变

原文链接 :https://blog.wanderview.com/blog/2017/03/13/firefox-52-settimeout-changes/?winzoom=1

参考:从setTimeout说事件循环
PS: 因为我英语不太好,基本上是借着google翻译的,所以受不了的可以直接点击原文查看。

你可能感兴趣的:(【译】Firefox 52 中对于 setTimeout() 的改动)