定时器工作原理

1,027 阅读5分钟


从根本上讲,了解 JavaScript 计时器的工作原理很重要。很多时候,它们的行为不直观,因为它们处于单线程中。

定时器函数

我们首先看看我们可以访问的三个构造和操作计时器的函数。

  • var id = setTimeout(fn, delay)

    启动一个定时器,它将在延迟后调用指定的函数。该函数返回一个唯一的 ID,可以在以后取消计时器。

  • var id = setInterval(fn, delay)

    类似于 setTimeout,但该函数会不断的被调用(每次都有延迟)直到它被取消。

  • clearInterval(id), clearTimeout(id)

    接受计时器 ID(由上述任一函数返回)并停止发生计时器回调。

定时器内部如何工作

为了理解定时器内部如何工作,需要探索一个重要概念:定时器延迟的时间是不确定的

由于浏览器中的所有 JavaScript 都在单个线程上执行,因此异步事件(例如鼠标单击和定时器)仅在执行进程中出现一个空位置时才运行。如下所示:

timers.png

垂直方向表示时间,以毫秒为单位。蓝色框代表正在执行的 JavaScript 部分。例如,JavaScript 的第一个块执行大约 18 毫秒,鼠标单击块大约执行 11 毫秒,依此类推。

由于 JavaScript 一次只能执行一段代码(由于其单线程特性),因此这些代码块中的每一个都 阻塞 了其他异步事件的进程。这意味着当异步事件发生时(如鼠标点击、定时器触发或 XMLHttpRequest 完成),它会排队等待稍后执行(这种排队的实际发生方式肯定因浏览器而异,因此请考虑这一点)。

首先,在 JavaScript 的第一个块中,启动了两个定时器:一个 10mssetTimeout和一个 10ms setInterval。由于定时器启动的时间和地点,它实际上在我们完成第一个代码块之前触发。但是请注意,它不会立即执行(由于线程的原因,它无法执行此操作)。而是将延迟的回调函数排队以便在下一个可用时刻执行。

此外,在第一个 JavaScript 块中,我们看到鼠标单击发生。与此异步事件关联的 JavaScript 回调(我们永远不知道用户何时可以执行操作,因此被认为是异步的)无法立即执行,因此,与初始定时器一样,它会排队等待稍后执行。

在最初的 JavaScript 块执行完后,浏览器会立即问一个问题:等待执行的是什么?在这种情况下,鼠标单击处理程序和计时器回调都在等待。然后浏览器选择一个(鼠标点击回调)并立即执行它。计时器将等待下一个可能的时间,以便执行。

请注意,当鼠标单击处理程序正在执行时,第一个 interval 回调也执行。与定时器一样,interval 的回调会排队等待稍后执行。但是请注意,当 interval 再次触发时(当定时器处理程序正在执行时),这次处理程序执行被删除。如果在执行大块代码时将所有间隔回调排队,结果将是一堆间隔执行,它们之间没有延迟,完成后。相反,浏览器倾向于简单地等待,直到没有更多的间隔处理程序排队(对于有问题的间隔),然后再进行更多的排队。

事实上,我们可以看到,当第三个 interval 回调在 interval 本身正在执行时触发,就是这种情况。这向我们展示了一个重要的事实:Intervals 并不关心当前正在执行什么,它们会不分青红皂白地排队,即使这意味着会牺牲回调之间的时间。

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

setTimeout 与 setInterval 的差异

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

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

乍一看,这两段代码在功能上似乎是等效的,但实际上并非如此。值得注意的是,setTimeout代码在上一次回调执行后总是至少有 10 毫秒的延迟(它可能最终会更多,但绝不会更少),而setInterval无论最后一次回调何时执行,代码都会尝试每 10 毫秒执行一次回调。

总结

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

  • JavaScript 引擎只有一个线程,强制异步事件排队等待执行。
  • setTimeout并且setInterval在执行异步代码的方式上是非常不一样的。
  • 如果定时器被阻止立即执行,它将被延迟到下一个可能的执行点(这将比所需的延迟更长)。
  • 如果间隔的执行时间足够长(长于指定的延迟),则它们可以无延迟地一个接一个的执行。

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