setInterval和任务队列

问题的来源

应该很多人都知道js单线程但是能完成异步的原因。
处理setTimeout的模块会在延迟时间后把要执行的任务推入任务队列,直到js线程上下文栈为空,那么setInterval这种每隔一段时间执行函数的情况呢?
(以下函数测试环境为Chrome)

实验一

1
2
3
4
5
6
var t = setInterval(function () {
console.log(new Date())
}, 1000)
var now = new Date()
console.log(now)
while (+new Date - +now < 5000) {}

实验一
这段代码意思就是创建了一个setInterval,每一秒打印当前时间,但是我用一段循环阻塞了5秒钟。
最开始的想法:每一秒钟,都会在任务队列中推入一个任务即打印当前时间。也就是阻塞5秒后会立即输出四行。
结果的话…
在5秒后,控制台慢慢悠悠地每隔一秒输出当前时间,也就是最开始的想法是不对的…

实验二

1
2
3
4
5
6
7
var t = setInterval(function () {
console.log(new Date())
}, 1000)
var now = new Date()
console.log(now)
while (+new Date - +now < 5000) {}
clearInterval(t)

是的我就加了一行clearInterval,也就是5秒循环后把定时器给停了。
结果倒是和我想的一样,5秒后什么都没发生…
实验二
如果在控制台console.log(t)会输出一个数字,或者控制台输入window.t会返回同样的数字。
到这里我的猜想是,浏览器模块处理一个setInterval时候只会把任务推入任务队列一次,直到js线程把任务取出执行,模块再根据延时把任务推入任务队列。

实验三

验证方法就是再来一个setInterval

1
2
3
4
5
6
7
8
9
var t = setInterval(function () {
console.log('t: ', new Date())
}, 1000)
var g = setInterval(function () {
console.log('g: ', new Date())
}, 2000)
var now = new Date()
console.log(now)
while (+new Date - +now < 5000) {}

结果:
实验三
当然如果在while后加一行clearInterval(t),那么输出的只有计时器g的结果了

脑测结论

  1. 处理setInterval的模块会保存这个计时器的状态,延时到了就会把任务推入任务队列,在该任务被取出之前,计时器就相当于一个静止状态。
  2. 如果遇到了clearInterval,那么模块中的Interval和任务都会被移除。

真正的原理不知道…

bug

看了一看实验三的结果,前两个g的输出时间仅差了一秒钟,后续的g则按照2秒输出一次的规则进行….这部分研究完再更新吧