JavaScript计时器的工作原理

原文地址: How JavaScript Timers Work

      在一个基础水平上了解JavaScript计时器的工作原理是非常重要的。由于它们处于单线程中,通常它们表现得不够直观。让我们从三个已经接触过的可以用来创建和操控计时器的函数开始本文。

  • var id = setTimeout(fn, delay);——初始化一个单独的计时器,它会在指定的延时之后调用指定的函数,并且返回一个可以用来取消计时器的ID。
  • var id = setInterval(fn, delay);——类似于setTimeout但持续调用函数(每次间隔一个延迟)直到被取消。
  • clearInterval(id);, clearTimeout(id);——接收一个计时器ID(由上述两个函数其中一个返回)并结束对该计时器回调函数的调用。

      为了从本质上了解计时器的工作原理,必须探讨一个重要的概念:不能保证计时器的延迟总是有效。因为在一个浏览器里的所有JavaScript代码都是在一个单线程里执行,异步事件(像鼠标点击和计时器)只有当执行域开放时才能执行(ps:这里译得有点别扭)。下面这张图很好地阐述了它的原理:

                                                                                                     

      这张图有很多信息可以挖掘,但是完全理解它能让你更好地领会异步JavaScript执行的工作原理。这张示意图是一维的:垂直方向上是以毫秒为单位的(挂钟)时间。蓝色的方框代表正在被执行的JavaScript代码的一部分。例如,第一个JavaScript程序块执行了大概18毫秒,鼠标点击模块执行了大约11毫秒,等等。

      因为JavaScript一次只能执行一块代码(由于单线程的特性),每个代码块都“堵住”了其他异步事件的处理。这意味着当一个异步事件发生时(像鼠标的点击、定时器的触发、XMLHttpRequest的完成),它会进入一个稍后将被执行的队列(至于这个排队实际上如何发生在不同的浏览器中表现也不一样,所以可以认为这是一种简化)。

      首先,在第一个JavaScript块里初始化了两个计时器:一个(延时为)10毫秒的setTimeout和一个(时间间隔为)10毫秒的setInterval。由于定时器启动的地方和时间(比较早),它实际上在我们完成第一个代码块之前就被触发了。然而,它并没有立即执行(由于线程的原因)。相反,被延迟的函数进入队列以便在下一个可用的时间执行。

      另外,我们可以看到在第一个JavaScript块里发生了一个鼠标点击事件。与这个异步事件(我们不可能知道一个用户会在什么时候采取一个动作,因此它被认为是异步的)相关的JavaScript回调函数不能马上执行,它也像开始时的计时器一样进入队列等待执行。

      在初始的JavaScript块执行之后,浏览器立即询问:有什么函数在等待执行?在上面的情况里,有一个鼠标点击的处理程序和一个计时器的回调函数在等待执行。浏览器选择一个(鼠标点击回调函数)马上执行,计时器(回调函数)则等到下一个可能的时间才执行。

      注意当鼠标点击处理程序执行时第一个interval回调函数执行了。和计时器一样,它的处理程序也被放进队列等待执行。然而,当interval再次触发时(计时器处理程序执行的时候),这一次处理程序被丢弃。如果你在一个大型的代码块执行时把所有的interval回调函数都放进队列里,会导致一堆intervals无间隔地执行直到完成。取而代之,浏览器往往等到队列里没有更多的interval处理程序时才把新的interval处理程序放进队列里。

      实际上,我们可以看到在上面的情形里,interval执行时第三个interval回调函数就触发了。这说明一个重要的事实:Intervals不关心现在正在执行什么,它们毫无章法地放进队列里,即使那意味着会牺牲掉回调函数之间的时间。

      最后,第二个interval回调函数完成之后,我们可以看到JavaScript引擎没有什么可执行的了。这意味着现在浏览器等待一个新的异步事件的发生。在上面的图中标记的50毫秒处有一个interval触发了。然而,这一次没有代码块堵住它的执行,所以它马上触发了。

      让我们看看下面的例子,以更好地说明setTimeout和setInterval之间的不同。

  setTimeout(function(){

    /* Some long block of code... */

    setTimeout(arguments.callee, 10);

  }, 10);

  

  setInterval(function(){

    /* Some long block of code... */

  }, 10);

      这两块代码第一眼看起来具有相同的功能,其实不然。值得注意的是,setTimeout代码在前一个回调函数执行完之后总是有至少10毫秒的延迟(可能多于10毫秒,不会更少)然而setInterval总是试图每隔10毫秒执行一次回调函数,不管上一个回调函数什么时候执行。

      从这里我们学到了很多,让我们概况一下:

  • JavaScript引擎是单线程的,迫使异步事件排队等待执行。
  • setTimeout和setInterval从根本上在它们执行异步代码的方式上是不同的。
  • 如果一个计时器被堵住不能马上执行它将会延迟执行直到下一个可能执行的时间点(比想要的延迟长)。
  • 如果每个interval回调函数执行时间够长(比指定的延迟时间长),Intervals可能背靠背没有延迟地执行。

      所有这些都是用来构建的非常重要的知识。知道一个JavaScript引擎如何工作,特别是在伴随着大量的经常发生的异步事件的情况下,将为构建一个先进的应用程序代码打下一个坚实的基础。

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页