setTimeout的实现及其问题

 

定义

定时器,用来指定某个函数在多少毫秒之后执行。它会返回一个整数,表示定时器的编号,同时你还可以通过该编号来取消这个定时器。

实现

定时器的实现

  1. 当通过 JavaScript 调用 setTimeout 设置回调函数的时候,渲染进程将会创建一个回调任务,包含了回调函数 showName、当前发起时间、延迟执行时间。
  2. 创建好回调任务之后,再将该任务添加到延迟执行队列中

延迟队列:维护了需要延迟执行的任务列表,包括了定时器和 Chromium 内部一些需要延迟执行的任务

  1. 处理完消息队列中的一个任务之后,就开始执行 ProcessDelayTask 函数。ProcessDelayTask 函数会根据发起时间和延迟时间计算出到期的任务,然后依次执行这些到期的任务。等到期的任务执行完成之后,再继续下一个循环过程。一个完整的定时器就实现了

ProcessDelayTask函数:专门用来处理延迟执行任务的

删除定时器的实现

直接从 delayed_incoming_queue 延迟队列中,通过 ID 查找到对应的任务,然后再将其从队列中删除掉就可以了。

存在的问题

如果当前任务执行时间过久,会影响定时器任务的执行,setTimeout是不准时的

通过前面setTimeout的实现来看,延迟队列的检查是在当前任务执行完之后进行的,如果当前任务执行过久,势必会影响后续的执行,可以这么认为,setTimeout设置的时间是将延迟任务由延迟任务队列添加到消息队列的时间而不是执行延迟任务的时间

如果 setTimeout 存在嵌套调用,那么系统会设置最短时间间隔为 4 毫秒

function cb() { setTimeout(cb, 0); }
setTimeout(cb, 0);
在 Chrome 中,定时器被嵌套调用 5 次以上,系统会判断该函数方法被阻塞了,如果定时器的调用时间间隔小于 4 毫秒,那么浏览器会将每次调用的时间间隔设置为 4 毫秒。实现的代码
static const int kMaxTimerNestingLevel = 5;
// Chromium uses a minimum timer interval of 4ms. We'd like to go
// lower; however, there are poorly coded websites out there which do
// create CPU-spinning loops. Using 4ms prevents the CPU from
// spinning too busily and provides a balance between CPU spinning and
// the smallest possible interval timer.
static constexpr base::TimeDelta kMinimumInterval = base::TimeDelta::FromMilliseconds(4);

延时执行时间有最大值

如果 setTimeout 设置的延迟值大于 2147483647 毫秒(大约 24.8 天)时就会溢出,那么相当于延时值被设置为 0 了,这导致定时器会被立即执行。

未激活的页面,setTimeout 执行最小间隔是 1000 毫秒

那就是未被激活的页面中定时器最小值大于 1000 毫秒,也就是说,如果标签不是当前的激活标签,那么定时器最小的时间间隔是 1000 毫秒,目的是为了优化后台页面的加载损耗以及降低耗电量。这一点你在使用定时器的时候要注意。

参考

浏览器工作原理与实践

whatwg规范