setInterval 执行过程?

我们需要知道,浏览器是个多线程应用,而Javascript是个单线程语言,当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中。 由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
a
注意:定时器指定的时间间隔,表示的是何时将定时器的代码添加到消息队列,而不是何时执行代码。所以真正何时执行代码的时间是不能保证的,取决于何时被主线程的事件循环取到,并执行。

如以下代码执行过程:

  1. setInterval(function() {
  2. // code ...
  3. }, 100)

image.png

  1. setInterval每隔100ms往队列中添加一个事件; 100ms 后,添加T1定时器至Task队列中,主线程中的执行栈有任务在执行,所以等待。
  2. some event 执行结束后,执行栈为空,于是去Task队列中拿出需要执行的代码,放至执行栈中执行,所以some event 执行结束后执行T1定时器代码
  3. 又过了100ms,T2定时器被添加到Task队列中,主线程还在执行T1代码,所以等待;
  4. 又过了100ms,理论上又要往Task队列中推一个定时器代码,但由于此时T2还在队列中,所以T3不会被添加,结果就是跳过
  5. 而且这里我们能够看到,T1定时器执行结束后立即执行了T2代码,所以并没有达到定时器的效果

所以我们能知道,setInterval有两个缺点:

  • 使用setInterval时,某些间隔会被跳过
  • 可能多个定时器会连续执行

所以我们这么理解: 每个setTimeout产生的任务会直接push到任务队列中;而setInterval在每次把任务push到任务队列前,都要进行一下判断(看上次的任务是否仍在队列中,在则跳过,不在则添加至Task队列)
**

如何模拟?

setTimeout模拟setInterval,也可理解为链式的setTimeout

  1. setTimeout(function() {
  2. // 任务
  3. setTimeout(arguments.callee, delay)
  4. }, delay)

这个模式链式调用了 setTimeout(),每次函数执行的时候都会创建一个新的定时器。第二个 setTimeout() 调用使用了 arguments.callee 来获取对当前执行的函数的引用,并为其设置另外一 个定时器。这样做的好处是,在前一个定时器代码执行完之前,不会向队列插入新的定时器代码,确保不会有任何缺失的间隔。而且,它可以保证在下一次定时器代码执行之前,至少要等待指定的间隔,避 免了连续的运行。这个模式主要用于重复定时器