本篇是John Resig的JS定时器工作原理的翻译版本,由于本人水平有限,仅供个人学习与参考,如有用词不准确,请以原文为准。

译文

在基本层面上,理解JavaScript定时器的工作原理是非常重要的。由于单线程的缘故,所以他们表现的不是很直观。让我们从研究可以构建和操作定时器的三个函数开始。

  • var id = setTimeout(fn, delay); - 初始化一个单独的定时器,它将在延迟结束后出发特定函数,函数将返回唯一ID,用它可以稍后取消这个定时器。
  • var id = setInterval(fn, delay); - 和上面setTimeout类似,但是它是连续不断地调用函数(每次都是一段延迟之后),直到它被取消。
  • clearInterval(id); - 接收一个ID(从上诉两个函数之一返回的),然后结束定时器。

为了理解定时器内部工作原理,要引出一个重要概念:定时器的延迟是不保证的。由于浏览器中的所有JavaScript都在单个线程上执行,因此只有在执行过程中有开始时才会运行异步事件(例如鼠标单击和计时器)。如下图所示:

js-timers

这个图中有很多信息要消化,但完全理解它会让您更好地了解异步JavaScript执行的工作方式。这个图片是一维的:垂直表示时间(毫秒),蓝块表示JS执行块,。比如第一块执行将近18ms,鼠标点击块大约11ms,等等。

因为JS在同一时刻只能执行一段代码(因为单线程机制),这些蓝“块”都在阻塞其他异步事件。意思是:当其他异步事件发生时(比如鼠标点击,定时器出发,XMLHTTPRequest完成),事件会进入到一个队列,以供之后执行(如何排队各个浏览器都不一致,所以认为是一种简化)。

开始,在第一块JS代码里初始化两个定时器:一个10ms的setTimeout和一个10ms的setInterval。由于计时器开始的位置和时间,实际上在我们完成第一个代码块之前就会触发它。但是请注意,它不会立即执行(由于线程,它不能执行此操作)。 相反,延迟功能排队以便在下一个可用时刻执行。

此外,在第一块代码里面,我们发现有一个鼠标点击事件触发。异步事件相关的JavaScript回调(我们不知道用户何时做这个动作,因此认为是异步)不能被立刻执行,像初始化好的定时器,排在队列中等待执行。

在第一段JS代码执行结束后,浏览器立刻“问”一个问题:下一个执行的是谁?在这种情况下,点击处理程序和定时器处理程序都在等待,所以浏览器选择了一个(鼠标点击回调),然后立刻执行。定时器将会等待下一个可用时间,等待执行。

请注意,在鼠标点击处理程序执行的时候,间隔定时器第一次触发了。与计时器一样,它的处理程序也在排队等待以后执行。然而,注意间隔定时器又一次触发了(此时timer正在执行),这次处理程序向下“顺延”了。如果一大块代码执行时,队列里有很多间隔执行器,那么每个间隔执行器将连续执行,没有间隔,直到完成(注:队列里间隔为空)。相反,浏览器只是简单地在等待直到没有更多的间隔处理程序排到队列中。

事实上,我们可以看出,间隔定时器第三次触发的时候,他自己本身正在执行(注:第二次正在执行)。这告诉我们一个很重要的事实:间隔定时器并不关心正在执行的是什么,他们会盲目的排队,即使两次触发之间的delay比预定的小。

最后,在第二次间隔回调函数执行结束后,我们可以看到JS引擎没有要执行的了。这也意味着浏览器在等待新的异步事件发生。当间隔定时器再次触发时,我们得到事件是50ms。这次,没有任何东西阻塞这次执行,所以立刻触发。

我们用一个例子来更好地展示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会尝试每10ms执行一个回调,不管上次执行。

我们学了很多知识,最后总结一下:

  • JavaScript引擎是单线程的,强制异步事件排队等待执行。
  • setTimeout和setInterval在执行异步代码上,从本质上来说是不一样的。
  • 如果定时器(timer)被正在执行块阻塞,他讲被延迟到下一个可用时间段来执行(将会比预期间隔更长)。
  • 间隔(intervals)将会连续执行,如果执行时间超过delay。

所有这些都是令人难以置信的重要知识。 了解JavaScript引擎的工作方式,特别是在发生大量异步事件的情况下,为构建高级应用程序代码打下了坚实的基础。

完!!!

关于作者

John Resig:

  • JS语言专家
  • Khan Academy前端架构师
  • 大名鼎鼎jQuery的作者
  • 畅销书《Secrets of the JavaScript Ninja》的作者。

翻译对照

  • interval: 间隔、间隔定时器
  • timer: 定时器
  • handler: 处理器
  • fire: 触发
  • queue up: 排队

个人心得

花了两个小时来翻译大牛的这篇技术博文,不仅锻炼了英语,还学习很多关于JS的内部工作原理,一个字:值!这篇对于理解JavaScript事件轮询机制也有重要帮助。