Javascript Timers时钟

Javascript的定时器时钟

setTimeout、setInterval是我们在写Javascript是常用的两个定时器方法,属于Timers,whatwg和w3c都对其进行了定义。其中有几点和时钟最小间隔相关的规定:

  1. w3c规定了setTImeout的最小间隔是4ms,setInterval是10ms;
    setTimeout

setInterval

  1. whatwg规定了嵌套层级小于等于5时,所有时钟的最小间隔是0ms,大于5时,所有时钟的最小间隔是4ms;
    nesting level is greater than 5
  2. 两个标准同时又允许进程繁忙和低电量时延长时间的最小间隔。
    user agents

规范在各环境实现的测试

规范虽然做了限制,实际情况如何表现,还是需要进行测试。
测试环境:
Windows: 7
Mac: 10.12.6
Chrome: 63.0.3223.0 canary 64-bit
Firefox: 57.0b2 (64-bit)
Safari: 11.0(12604.1.38.7)

IOS:11.0
Android: Samsung Calaxy Note-4(SM-N9108V)
UC:V11.3.0.907
Chrome: 61.0.3168.98

测试单次setTimeout(fn,0),参考demo_1

Windows

激活标签 IE8 IE9 IE11 Chrome Firefox
≈ 16ms ≈ 15ms ≈ 4ms ≈ 1ms ≈ 1ms

Mobile

激活标签 Android Browser UC Chrome Safari
≈ 4ms ≈ 4ms ≈ 4ms ≈ 10ms to 20

Mac 电源模式

激活标签 Chrome Firefox Safari
≈ 1 or 2ms ≈ 0 or 1ms ≈ 2 to 6ms

Mac 电池模式

激活标签 Chrome Firefox Safari
≈ 1 or 2ms ≈ 0 or 1ms ≈ 2 to 6ms

测试递归setTimeout(fn,0),参考demo_2

Windows

激活标签 IE8 IE9 IE11 Chrome Firefox
≈ 16ms ≈ 16ms ≈ 4ms ≈ 4ms ≈ 4ms
≈ 16ms ≈ 16ms ≈ 15ms ≈ 1,000ms ≈ 1,000ms

Mobile

激活标签 Android Browser UC Chrome Safari
≈ 4ms ≈ 5ms ≈ 5ms ≈ 4ms
≈ 1,000ms ≈ 5ms ≈ 1,000ms 冻结

Mac 电源模式

激活标签 Chrome Firefox Safari
≈ 1 or 2ms ≈ 0 or 1ms ≈ 2 to 6ms
≈ 1,000 to 10,000ms ≈ 1,000ms ≈ 1,000 to 50,000ms

Mac 电池模式

激活标签 Chrome Firefox Safari
≈ 1 or 2ms ≈ 0 or 1ms ≈ 2 to 6ms
≈ 1,000 to 10,000ms ≈ 1,000ms ≈ 1,000 to 50,000ms

测试非激活标签setTimeout(fn,0),多层嵌套,参考demo_3

Windows

激活标签 IE8 IE9 IE11 Chrome Firefox
≈ 16ms ≈ 16ms ≈ 4ms ≈ 4ms ≈ 4ms
≈ 16ms ≈ 16ms ≈ 15ms ≈ 1,000ms ≈ 1,000ms

Mobile

激活标签 Android Browser UC Chrome Safari
≈ 2ms to 5ms ≈ 2ms to 5ms ≈ 5ms ≈ 5ms
≈ 1000ms ≈ 2ms to 5ms ≈ 1000ms 冻结

Mac 电源模式

激活标签 Chrome Firefox Safari
≈ 1 to 5ms ≈ 1ms to 5ms ≈ 1ms to 5ms
≈ 10,000ms ≈ 1,000ms ≈ 1,000 to 50,000ms

Mac 电池模式

激活标签 Chrome Firefox Safari
≈ 1 to 5ms ≈ 1ms to 5ms ≈ 1ms to 5ms
≈ 10,000ms ≈ 1,000ms ≈ 1,000 to 50,000ms

测试setInterval(fn,0),参考demo_4

Windows

激活标签 IE8 IE9 IE11 Chrome Firefox
≈ 16ms ≈ 16ms ≈ 4ms ≈ 4ms ≈ 4ms
≈ 16ms ≈ 16ms ≈ 15ms ≈ 1000ms ≈ 1000ms

Mobile

激活标签 Android Browser UC Chrome Safari
≈ 4ms ≈ 5ms ≈ 4ms ≈ 5ms
≈ 1,000ms ≈ 5ms ≈ 1,000ms 冻结

MacBook 电源模式

激活标签 Chrome Firefox Safari
≈ 4ms ≈ 5ms ≈ 5ms
≈ 1,000ms ≈ 1,000ms 1,000 to 50,000ms

MacBook 电池模式

激活标签 Chrome Firefox Safari
≈ 4ms ≈ 5ms ≈ 5ms
1,000 to 10,000ms ≈ 1,000ms 1,000 to 50,000ms

测试分析

上文的测试并不能覆盖所有的场景和浏览器,只是作为一个参考。而且从测试的方法也受到其它因素的影响,如硬件、系统时钟的设计,但这并不能妨碍我们总结出一些结论,最新的浏览器在页面激活且系统空闲的的状态下,基本都能够按照标准执行 ≈ 4ms 的最小时钟,但在非激活状态,情况就比较复杂了,从1,000ms到40,000ms,甚至是冻结时钟。

chromium Timers实现

我阅读chromium的代码,希望能够进一步了解其如何实现时钟的变化。
time目录是chromium定义时钟的代码,time.h针对Linux、Mac、Windows进行了兼容,加载了不同操作系统的库,用于实现获取时间的接口。
这里涉及到Unix/Linux与Windows底层时钟实现方式的区别:时钟精确度。Unix/Linux的底层能够提供精确的时钟函数,例如[clock_gettime]13、[CFAbsoluteTimeGetCurrent]14等,它们都能够提供毫秒级别的时间函数。但在Windows平台,系统时钟并非精确到毫秒级,系统默认的时钟间隔是15.6ms。
Win32 API Timer 这里介绍了Windows平台提供的时钟接口,chromium for windows(time_win.cc),如果检测到支持高精度时钟,chromium会选择QueryPerformanceCounter(QPC,微秒级),否则使用TimeGetTime(精度与机器相关,默认大概15.6ms,但可以通过 timeBeginPeriod和timeEndPeriod修改默认精度),chromium在默认情况下会设置成4ms,但如果使用电池,会重置成默认精度。
上文只是设置了系统时钟,setTimtout和setInterval等Javascript时钟函数,DOMTimer.cpp里面已经写死了最小值就是4ms,所以4ms - 15.6ms成了这两个函数的精度。

在前文我们测试的结果包括未激活状态下的标签页间隔的情况,在chromium的代码内这部分代码比较复杂,chrome 57对非激活标签页进行了优化,该优化会将时钟间隔延长到任务时长的100倍,这个例子可以很清晰地看出结论。
总的来说,操作系统的限制、浏览器的内核决定了setTimeout和setInterval的精度的变化,且该变化在不同状态下有着不同的表现。

Timers对开发的影响

  1. 动画一般要求精确控制每一帧的时钟,否则会产生掉帧的问题,但Timers本身的精确性受浏览器和进程繁忙程度的影响,无法,不推荐使用;
  2. setTimeout(fn,0)事实上并不是0ms后执行,在现代浏览器中可以使用新的方式window.postMessage()来替代;
  3. 在其它一些需要延迟执行的场景,比如通过延迟使浏览器其它线程完成执行(页面渲染等),Timers API还是很重要的。

你可能感兴趣的:(Javascript Timers时钟)