梦里不觉秋已深

余情岂是为他人

深入理解Javascript定时器是如何工作的

[译]JavaScript中的定时器是如何工作的?

如有问题,欢迎指正

原文链接:http://ejohn.org/blog/how-javascript-timers-work/

在一个基础阶段,理解JavaScript定时器的工作原理的是非常重要的。通常它们看起来不那么直观,因为它们处于单线程中。让我们从我们将接触的这三个函数开始,它们是用来构造和操作计时器的。

  • var id = setTimeout(fn, delay) – 启动一个定时器,它将调用参数中的函数在延迟时间之后。这个函数将将返回一个独一无二的ID用来被指定以取消这个定时器。
  • var id = setInterval(fn, delay) – 和setTimeout()相似,但将持续地调用参数中的函数每间隔一个延迟时间,除非它被取消。
  • clearInterval(id)clearTimeout(id) – 传入一个定时器ID(即上述函数传回的ID)并停止定时器回调。

为了理解定时器内部是如何工作的,还有一个重要的概念需要理解就是:定时器的延迟时间是无法保证的。由于浏览器中的所有JavaScript都在单个线程上执行,因此异步事件(例如鼠标点击和计时器)只会在执行中出现打开时运行。这最好以图表来解释,如下图:

Timers

这张图中包含了大量的信息要研究,但理解它会让你对异步JavaScript执行是如何工作有一个更深刻的认识。这张图是一维的,在垂直方向我们能看到图中标着事件,以毫秒为单位。蓝色的盒子代表着JavaScript正在被执行的部分。例如第一块JavaScript大概执行18毫秒,鼠标点击那块大约11毫秒等等。

因为JavaScript一次只能执行一段代码(由于它的单线程特性),所以每个代码块都“阻塞”了其他异步事件的进度。这意味着当一个异步事件发生时(如鼠标点击,计时器触发或是XMLHttpRequest完成),它会排队等待稍后执行。(实际中排队是如何进行的,不同的浏览器情况不同,因此这里我们考虑的时候将它理想化)。

首先,在第一个JavaScript块中,启动了两个计时器,分别是10毫秒的setTimeout()和10毫秒的setInterval()。虽然我们可以清楚地看到它是何时何地计时器被触发,但实际上这发生在第一个代码块完成之前。但是,请注意,它并没有马上被执行(它不能这样做,因为线程的原因)。相反地,延迟功能被排队,以便在下一个可以被执行的时刻执行。

此外,在第一个JavaScript块中,我们看到发生了一个鼠标点击事件。于是与这个异步事件关联的JavaScript回调(由于我们不知道用户何时会执行一个动作,因此我们认为它是异步的)不能立即执行。因此和初始的定时器类似,它被排队等待执行。

在JavaScript完成执行浏览器的初始块之后立即提出了问题:接下来是什么等待被执行?此时鼠标点击程序和计时器回调都在等待。于是浏览器选择了一个(鼠标点击回调)并立刻执行了它。于是计时器将等到下一次可能执行的时间以便执行。

注意当鼠标点击程序在执行时,第一个间隔回调也执行了。与定时器一样,其处理程序也排队等待执行。但是注意,当间隔再次被触发时(同时计时器程序正在执行),这次将删除处理程序。如果在一大块代码正在被执行的时候将所有的间隔回调排队等待执行,那么当这一大块代码完成后将是一大堆间隔执行程序,并且它们之间没有延迟。相反,在给更多的间隔处理程序排队前,浏览器往往只是等待直到没有更多的间隔处理程序在排队。

事实上,我们可以看到,这是第三个间隔回调触同时间隔处理程序本身正在被执行的情况。从中我们可以发现一个事实,就是:间隔程序并不在意正在执行的是什么,它们会不加区别地排队,即使这意味着回调之间的时间会被牺牲。

最终,在第二个间隔回调被执行后,我们可以看到这里没有东西等待JavaScript引擎去执行。这意味着浏览器现在等待新的异步事件发生。这发生在50毫秒标记处,也就是间隔再次被触发。但这一次没有东西阻塞它的执行,所以它立刻被触发了。

让我们从下面的例子中更好的理解setTimeout()setInterval()之间的区别。

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

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

这两段代码乍一看在功能上看起来似乎是一样的,但实际上不是的。值得注意的是,在前一个回调执行之后,setTimeout()至少会有10毫秒的延迟。(它可能会更多,但至少不会少),而setTnterval()会尝试每10毫秒执行一次回调,而不考虑何时执行最后一次回调。

到目前为止我们学了很多,让我们总结一下:

  • JavaScript引擎只有一个线程,也就是单线程,迫使异步事件排队等待执行。
  • setTimeout()setInterval()在如何执行异步代码上有根本的不同。
  • 如果一个计时器被阻塞无法立即执行,它将被推迟到下一个可能执行的时刻(这将导致它比期望的延迟时间更长)。
  • 如果它将花费足够长的时间(超出了指定的推迟时间),间隔将会一个接着一个地无间隔执行。

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

发表评论

电子邮件地址不会被公开。 必填项已用*标注