[译]JS 定时器工作原理

时间:2021-01-18 00:10:01

   在对JS本质理解的层面上,重要的是了解JS定时器是如何工作的。很多时候我们觉得定时器的执行不够直观,那因为它们运行在单线程的环境里。我们先来仔细观察下面三个js的内置函数,然后我们再具体去使用它们:

1.var id= setTimeout(fn,delay);
说明:setTimeout这个内置函数的功能是启动一个定时器,这个定时器在指定的延迟时间 (delay,单位为毫秒(ms)) 过后,将调用指定的回调函数(fn),函数调用结束后会返回一个唯一的id,需要时可以把这个id专递给clearTimeout(id)内置函数,以便取消定时器,这个内置函数只执行一次。
2.var id= setInterval(fn,delay);
说明:跟setTimeout内置函数类似,不同的地方是setInterval会以指定的延迟时间(delay指定的毫秒(ms))为时间间隔,不停的调用指定的回调函数,迟到调用clearInterval(id)函数取消调用。
3.clearTimeout(id);clearInterval(id);
说明:clearTimeout和clearInterval接受一个id(这个id是上述两个函数之一的返回值,分别对应为,setTimeout->clearTimeout,setInterval->clearInterval)然后停止回调(结束对fn的调用)。
  为了理解定时器内部是如何工作的,这里有一个重要的概念需要我们来探索: 定时器延迟是没有保证的。因为所有的javascript在浏览器中执行单线程异步事件(比如鼠标点击或定时器),并且这个异步事件只有存在一个开口的情况下才能够执行。下面这张图片是最好的例子:
[译]JS 定时器工作原理
 

上面这张图片有很多信息可以挖掘,要是能够完全的理解它,那将让你更好的去领悟javascript异步工作是如何实现的。这是一张一维的图片:从上到下 标注了执行的时间(钟表),单位是毫秒(ms),每个方框表示javascript被执行的一部分。例如第一个方框的javascript大约执行了18ms,而鼠标点击事件被阻塞了大约11ms等等。

  由于在同一时间内只能执行一个javascript代码块(由于它的单线程特性),每一个这样的代码块都会“阻塞”其他异步事件的执行。这意味着当一个异步事件出现时(例如鼠标点击、定时器触发、一个XMLHttpRequest请求完成),它会排队等待被执行(由浏览器到浏览器如何确定这个队列真正发生了呢,所以考虑到这些时,我们将会简化说明).

  以在第一个javascript代码块内作为开始,有两个定时器被启动:一个10ms的setTimeout和一个10ms的setInterval;由于何时何地定时器被启动,它实际触发在我们实际完成第一个代码块之前。注意,然而它并没有立即被执行(他不能执行是因为单线程的原因),取而代之延迟函数(非fn,是delay的fn)被放进队列里面,只是为了等待可用的时间片段然后再执行。

  另外,在第一个javascript代码方框内,我们看到鼠标点击事件出现了,鼠标对应的回调函数与这个异步事件(我们从未得知何时用户完成一个操作,因此鼠标点击事件它认为这是异步的)相联系,但是这个回调函数并不能立即被执行,就比如启动了一个定时器,它会排队等待被执行。

  当一个javascript代码块执行完成之后,浏览器会去问一个问题:是什么代码块在等待被执行?在这样的情况下,鼠标点击事件处理函数和触发器回调函数两者都等待被执行,浏览器然后会选择一个代码块(鼠标点击处理函数)并且立即执行它,而定时器为了执行回调函数会继续的在等待下去,迟到出现可利用的时间片刻。

  注意,当鼠标点击事件处理程序正常执行时,第一个间接回调函数(是setInterval中的delay对应的Interval回调函数)也执行,而它对应的处理程序和触发器仍在等待以后被执行。然而,值得注意的是,当这个间接回调函数再次触发时(当触发器处理程序正在执行时),此时另一个处理程序的执行将被放弃。当一个大的代码块正在执行时,如果你是去排队所有的Interval回调函数,那么结果将会是一堆Interva回调函数被执行,他们之间不会存在延迟,在完成之前。然而,浏览器们总是趋向于一味的等待没有更多的Interval回调函数被排队(之前有很多Interval回调函数在排队)。事实上,我们可以看到第三个Interval回调函数触发时,它自身正常执行。这些给我们说明了一个事实:Interval不关心什么是当前正在执行的,他们将进入到队列里面去,即使那意味着将会牺牲掉回调处理函数之间的时间间隔。

  最后,在第二个 Interval回调函数被执行完成之后,我可以看到没有什么需要javascript引擎去执行。这意味着浏览器正在等待一个新的异步事件出现。在50ms标记的地方,当interval再次触发时我们可以看到这样的一个事实,然而这次没有任务代码块阻塞其执行,所以它立即触发。

  为了更好的举例说明setTimeout和setInterval的区别,我们来看看一个例子:

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

咋一看,这个两个代码块看起来功能是相同的,其实他们并不一样,尤其是setTimeout在前一个回调执行完之后,总是延迟至少10ms(可能更多,但从不了不会减少),然而setInterval每十秒都会企图尝试执行回调函数,不管何时最后一个回调被执行完。

   上面我们在这里学习了很多内容,现在让我一起来总结一下:

1.javascript引擎只有一个线程,强迫异步事件排队等待被执行。

2.setTimeout和setInterval本质上不同的地方是他们如何执行异步代码的。

3.如果一个定时器正在执行的时候被阻塞了,那么它将会被推迟到下一个可能的执行点,这既是使得延迟时间有可能会超过声明定时器时设置的值。

4.Interval如果有足够的时间来执行(大于制定的延迟),那么它将会无延迟的一个紧接着一个执行。

 在通常情况下,对于构建一个高级的大规模javascript工程时,将会出现大量的异步事件,如何处理好这些问题就得依靠积累和深入体会上面这些知识,知道javascript引擎是如何工作的,这是很重要也很难以置信的。

 

原文出自: John Resig