Javascript定时器如何工作(译)

从原理上了解JS定时器是如何工作是一件很重要的事。因为JS是单线程的,所以大多数时候定时器都会表现的不直观。让我们来看看我们所能看到的三个定时器函数,用他们来构建和操作定时器。

  • var id = setTimeout(fn, delay);-初始化一个单独的定时器,定时器会在指定的延迟之后执行特定的函数,同时这个函数会返回一个唯一的ID,后面可以通过这个ID清除定时器。
  • var id = setInterval(fn, delay);-和setTimeout很类似,但是setInterval会重复执行回调函数直至被取消。
  • clearInterval(id); clearTimeout(id);-接受一个定时器ID同时停止执行该定时器的回调函数。

为了理解定时器在内部是如何工作的,这里有一个重要的概念来解释一下:定时器的延迟时间并不是一定的。由于浏览器中所有Javascript都是在单线程中执行的,因此异步事件(类似鼠标点击或者定时器)至于在执行时才会出现。下图是最好的演示:

定时器

这张图中有很多重要的信息,当你完全理解之后你将对JS如何执行异步操作有更深入的理解。这是一张一维的图片:垂直方向是以毫秒为单位的时间。蓝色盒子代表有JS代码正在被执行。举例来说,第一个JS代码执行大约18毫秒,鼠标点击事件大约执行了11毫秒等等。

由于Javascript一次只能执行一段代码(因为单线程的缘故),每个代码块都会阻塞其他异步事件。这意味着异步事件(鼠标点击、定时器执行、XMLHttpRequest完成)出现后不会马上就执行而是先加入到队列中(每个浏览器如何实现这种排队方式是不同,所以我们可以认为上图是一个简化版的排队方式)。

下面开始解释上面的图片,当第一段代码运行的时候,有两个定时器启动了:一个10ms的setTimeout和一个10ms的setInterval。定时器何时被执行完成由我们什么时候完成第一个代码块决定。但是,请注意,它不会马上执行(因为线程的缘故,所以他不能执行)。在下一个空闲时间,这些在队列中延后的函数将被顺序执行。

另外,在第一个Javascript代码块执行时,我们看到有一个鼠标点击事件出现。与异步事件相关联的JS回调不会马上执行(我们永远不知道用户何时可能执行一个动作,所以我们认为它是异步的),就像初始的定时器,他将排队等待执行。

当第一块JS执行完成之后,浏览器马上就会问:还有谁在等待被执行?在这个例子中,有一个鼠标点击事件和一个定时器的回调在等待执行。定时器将会等待下一个可执行的时间去执行。

注意,当鼠标点击事件执行的时候,第一个间隔回调也开始执行了。和定时器一样,他的处理函数将会排队等待后续的执行。但是,请注意,当间隔定时器再次被触发,这次处理函数将被丢弃。如果在你使用interval时,有一大段代码要执行,结果将会是你所有的回调都会没有间隔的在其他代码执行后连续执行。相反,浏览器更倾向于等待至没有其他处理函数排队。

事实上,我们可以看到当interval正在执行中,第三个interval回调被触发了。这告诉我们一个很重要的事实:interval并不关注当前什么正在执行,他将不加区别的插入到队列中,即使这意味着回调之间的时间将被牺牲掉。

最后,在第二个间隔回调完成执行后,我们可以看到没有任何东西留给JavaScript引擎执行。这意味着浏览器现在等待一个新的异步事件发生。我们得到这个在50ms标记时间间隔再次发生。 这一次,没有阻塞其执行,所以它立即引发。

让我们来看一个例子来更好的说明setTimeoutsetInterval之间的区别。

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

setInterval(function () {
    /* Some long block of code… */
}, 10);

这两段代码看起来似乎是相等的,但是他们并不是这样的。setTimeout将回调执行完成后至少10ms执行,而setInterval将尝试每隔10ms执行一次,不考虑最后一次回调什么时候执行。

我们在这里学到了很多,让我们回顾一下:

  • JS引擎只有一个线程,将会强制异步事件排队执行
  • setTimeoutsetInterval在执行异步代码时有根本的区别
  • 如果定时器被阻止立即执行,它将被延迟,直到下一个可能的执行点(这将长于所需的延迟)
  • 如果interval的回调执行时间长于指定的延迟,interval或许将无间隔的一个接一个执行

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