从根本上讲,了解 JavaScript 计时器的工作原理很重要。很多时候,它们的行为不直观,因为它们处于单线程中。
定时器函数
我们首先看看我们可以访问的三个构造和操作计时器的函数。
-
var id = setTimeout(fn, delay)
启动一个定时器,它将在延迟后调用指定的函数。该函数返回一个唯一的 ID,可以在以后取消计时器。
-
var id = setInterval(fn, delay)
类似于 setTimeout,但该函数会不断的被调用(每次都有延迟)直到它被取消。
-
clearInterval(id)
,clearTimeout(id)
接受计时器 ID(由上述任一函数返回)并停止发生计时器回调。
定时器内部如何工作
为了理解定时器内部如何工作,需要探索一个重要概念:定时器延迟的时间是不确定的
。
由于浏览器中的所有 JavaScript 都在单个线程上执行,因此异步事件(例如鼠标单击和定时器)仅在执行进程中出现一个空位置时才运行。如下所示:
垂直方向表示时间,以毫秒为单位。蓝色框代表正在执行的 JavaScript 部分。例如,JavaScript 的第一个块执行大约 18 毫秒,鼠标单击块大约执行 11 毫秒,依此类推。
由于 JavaScript 一次只能执行一段代码(由于其单线程特性),因此这些代码块中的每一个都 阻塞
了其他异步事件的进程。这意味着当异步事件发生时(如鼠标点击、定时器触发或 XMLHttpRequest 完成),它会排队等待稍后执行(这种排队的实际发生方式肯定因浏览器而异,因此请考虑这一点)。
首先,在 JavaScript 的第一个块中,启动了两个定时器:一个 10mssetTimeout
和一个 10ms setInterval
。由于定时器启动的时间和地点,它实际上在我们完成第一个代码块之前触发。但是请注意,它不会立即执行(由于线程的原因,它无法执行此操作)。而是将延迟的回调函数排队以便在下一个可用时刻执行。
此外,在第一个 JavaScript 块中,我们看到鼠标单击发生。与此异步事件关联的 JavaScript 回调(我们永远不知道用户何时可以执行操作,因此被认为是异步的)无法立即执行,因此,与初始定时器一样,它会排队等待稍后执行。
在最初的 JavaScript 块执行完后,浏览器会立即问一个问题:等待执行的是什么?在这种情况下,鼠标单击处理程序和计时器回调都在等待。然后浏览器选择一个(鼠标点击回调)并立即执行它。计时器将等待下一个可能的时间,以便执行。
请注意,当鼠标单击处理程序正在执行时,第一个 interval 回调也执行。与定时器一样,interval 的回调会排队等待稍后执行。但是请注意,当 interval 再次触发时(当定时器处理程序正在执行时),这次处理程序执行被删除。如果在执行大块代码时将所有间隔回调排队,结果将是一堆间隔执行,它们之间没有延迟,完成后。相反,浏览器倾向于简单地等待,直到没有更多的间隔处理程序排队(对于有问题的间隔),然后再进行更多的排队。
事实上,我们可以看到,当第三个 interval 回调在 interval 本身正在执行时触发,就是这种情况。这向我们展示了一个重要的事实:Intervals 并不关心当前正在执行什么,它们会不分青红皂白地排队,即使这意味着会牺牲回调之间的时间。
最后,在第二个间隔回调执行完成后,我们可以看到 JavaScript 引擎没有任何东西可以执行。这意味着浏览器现在是等待新的异步事件发生。当间隔再次触发时,我们在 50ms 标记处得到这个。然而,这一次没有任何东西阻止它的执行,所以它会立即触发。
setTimeout 与 setInterval 的差异
让我们看一个例子,以更好地说明setTimeout
和setInterval
之间的差异
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 引擎的工作原理,尤其是处理通常发生的大量异步事件时,可以为构建高级应用程序代码奠定良好的基础。