JavaScript的定时器是如何工作的

ThinkingLink 2019-12-06

理解JavaScript定时器工作原理对于学习JavaScript非常重要。因为JavaScript是单线程运行的,定时器使用场合少,不是很直观。下面通过三个函数来学习JavaScript如何定义,操作及销毁一个定时器。

  • var id = setTimeout(fn, delay); - 定义一个定时器,在指定时间delay后调用函数fn。函数返回一个唯一的标识ID,如果不需要使用这个定时器可以用这个取消。
  • var id = setInterval(fn, delay);- 类似setTimeout,但是会每隔指定时间delay调用指定函数fn,直至取消这个定时器。
  • clearTimeout(id);clearInterval(id); -这两个函数接受一个标识ID(分别对应上面两个函数返回的ID),停止定时器的回调。

 要理解定时器如何工作,首先要弄清楚一个概念,定时器的回调函数不能保证在指定时间delay一定执行。由于浏览器中的所有JavaScript都在单线程上执行,因此异步事件(例如鼠标单击和计时器)仅在执行中存在同步时间执行完有空缺时才运行。参考下图:

JavaScript的定时器是如何工作的

这张图里面包含很多信息,要想理解他们需要首先理解JavaScript异步运行机制。这张图中垂直方向上有时间刻度,以毫秒为单位,蓝色部分表示正在执行的JavaScript事件。例如,第一块JavaScript执行大约18毫秒,Mouse Click Callback大约执行11毫秒,后面以此类推。

由于JavaScript是单线程的,不能同时执行两段JavaScript代码,所以上面的蓝色的”块“都是在上一个块执行完才能执行下一个块。这意味这当一个异步任务(例如,点击鼠标事件,定时器,ajax访问)出现的时候,它将被放入到异步队列(放入队列的方式和浏览器有关,不同浏览器有不同的实现)并随后执行。

首先,在第一个代码块中,JavaScript代码中首先出先两个定时器:10ms Timer starts,10ms Interval starts。这两个定时器的回调函数何时执行取决于第一个代码块的所有代码何时执行完。请注意,由于单线程的原因,它不会立即执行。

同时,在第一个代码块里,还有一个鼠标事件Mouse Click Occurs,和上面的定时器一样,这个异步事件(点击鼠标这种用户交互是异步执行的,因为JavaScript不知道用户什么时候会点击鼠标)的回调不会立即执行,而是放在异步队列里排队等待执行。

在初始的代码块执行完毕之后,浏览器随即开始轮询这个异步队列:有那些操作等待着被执行呢?在这个例子中,鼠标点击。浏览器会选择一个立即执行(这里是鼠标点击的回调时间)。定时器会等待指定的时间delay,然后执行。

注意点击鼠标回调事件在第一个事件循环,定时器回调在随后的循环中处理。但是,在后面的事件循环中(在执行定时器处理程序时),setTimeout定时器回调函数就会被抛弃,不再执行了。如果在多个定时器之后有一大段同步任务执行,则同步任务执行完之后这些定时器回调会被立即执行,没有延迟(这个延迟可能很小,就是事件循环的间隔),直至完成。浏览器倾向于等到没有更多的异步任务被加入到异步任务队列中再开始执行。

实际上,我们可以看到在事件循环本身正在执行的同时触发了第三个回调的情况。 这向我们显示了一个重要的事实:事件循环不关心当前正在执行的内容,它们会不加区分地排队,即使这意味着从触发事件,到满足条件执行回调函数之间,有一部分事件被浪费掉了。

最后,在第二个事件回调完成执行之后,我们可以看到JavaScript引擎没有执行的剩余内容。 这意味着浏览器现在等待新的异步事件发生。 当间隔再次触发时,我们在50ms处获得此值。 但是,这次没有任何任务阻止它的执行,因此它自己立即触发。

让我们看一个示例,以更好地说明setTimeoutsetInterval之间的差异。 

setTimeout(function(){
  /* Some long block of code... */
  setTimeout(arguments.callee, 10);
}, 10);
 
setInterval(function(){
  /* Some long block of code... */
}, 10);

两者的不同之处在于setTimeout在在10毫秒的delay之后执行代码(只会多余10毫秒,绝不会少),setInterval则在每隔10毫秒的延迟时执行回调代码,不管上次的回调是否已经执行完。

好了,本文中介绍的一些要点,现在回顾一下:

  • JavaScript引擎在运行时只有一个线程,从而迫使异步事件排队等待执行。
  • setTimeout和setInterval在执行异步代码的方式上不同。
  • 如果定时器被阻止立即执行,它将延迟到下一个可能的执行点(比所需的延迟时间更长)。
  • 如果定时器延迟的事件足够长,则在到点后会立即执行,没有延迟。

所有这些都是非常重要的基础知识。 了解JavaScript引擎的工作原理,尤其是在通常发生大量异步事件的情况下,为构建高级应用程序代码奠定了良好的基础。

来源:https://johnresig.com/blog/how-javascript-timers-work/

本文是John Resig很早的一篇文章,通过setTimeout,setInterval两个函数的比较,可以了解JavaScript事件循环机制。

相关推荐