javascript计时器的工作原理解析

心已入冬 提交于 2020-02-29 17:50:48
代码使用方法:

说明:这篇文章节选自John Resig 的《Secrets of the JavaScript Ninja》一书,本人翻译只是供大家学习,翻译不足之处,请斧正。

这篇文章主要从下面几个方面解读计时器:

计时器概述;
计时器速度深度探析;
用计时器处理大量任务;
利用计时器管理动画;
较好的计时器测试

计 时器是一个我们了解很少且经常被滥用的东西,它是javascript的特色。实际上,在复杂的应用程序开发中,它能为我们提供很多帮助。计时器提供了一 个可以将代码片段异步延时执行的能力,javascript生来是单线程的(在一定时间范围内仅一部分js代码能运行),计时器为我们提供了一种避开这种 限制的方法,从而开辟了另一条执行代码的蹊径。

有趣的是,与我们普遍接受的观点相反,计时器并不是javascript语言的一部分,而是浏览器引入的方法和对象的一部分。这意味着如果你选择在一个非浏览器的环境运行它,很有可能计时器不存在,你必须使用特定功能推行你自己的版本(如Rhino线程)。

1、计时器是如何工作的

从根本上来说,理解计时器如何工作很重要。通常情况下,计时器的行为并不直观,因为它在一个单独的线程中,让我们从三个函数的测试开始,对于每一个函数我们都有机会构建和控制计时器。

var id = setTimeout(fn,delay);启动一个计时器,它将在延迟时间之后调用特定的函数,该函数返回一个唯一的ID,利用这个ID计时器在稍后的时间里被取消;
var id = setInterval(fn,delay);与setTimeout相似,但它不断的调用函数(每隔一定延迟时间)直到它被取消;
clearInterval(id),clearTimeout(id);接受计时器的 ID(由上述任意一个函数返回)并停止调用计时器。

为了理解计时器内部是如何工作的,有一个很重要的概念需要加以探讨:延迟是无法保证的。既然浏览器中所有javascript 是在一个单线程中运行的,那么异步事件(如鼠标点击、计时器)在执行中也只有存在开放状态时才运行,下面这张图很好的说明了这个问题:



这张图有很多信息需要消化,充分理解它将使你对异步js执行有一个更好的认识,图表是一维的,在垂直方向上是时间(挂钟),以毫秒为单位。蓝色盒子代表js执行的比例。例如,第一个javascript块运行时间大约为18秒,鼠标点击大约为11秒等等。


既 然javascript在一定时间内之执行一部分代码(源于单线程的特性),那么这些代码块的每一个就被封锁在其它异步事件执行的进程中。这表明当一个异 步事件发生时(如鼠标点击、计时器释放、XMLHttpRequest请求完成),它将排队等候执行(如何排队在不同浏览器之间是不一样的)。

首 先,在第一代码块里,有两个计时器触发:一个是10ms的setTimeout,一个是10ms的setInterval。在第一个代码块真正完成之前, 它实际上已经释放了。但是,注意,它不会立即执行(由于单线程的问题,它无能为力),相反,为了能在下一个可行的时间得到执行,那些延时函数被编入队列。

另外,在第一个代码块内,我们看到鼠标点击出现。与这个异步事件(我们永远不知道何时执行动作,这样就可以认为它是不同步的)相关Javascript回调函数跟初始的计时器一样不能立即被执行,它排队等候执行。

在Javascript最初的代码块执行完毕之后,浏览器会发出疑问:正在等候执行的是什么?在这种情况下,鼠标点击处理器和计时器回调函数同时处于等待之中,然后,浏览器将选择一个并立即执行它,计时器函数将等到下一个可能的时间执行。

注 意,当鼠标点击函数处理器执行时,第一个回调函数也在执行,至于计时器,其处理器被编入队列稍后执行。但是,请注意,当Interval再次释放时(在计 时器处理器执行时),计时器执行的时间将减少。如果你在一大块代码执行期间将所有的Interval回调函数编入队列,其结果是一大群Interval回 调函数会毫无延迟的执行,直到全部完成。而浏览器在队列增大之前只是简单的等到没有Interval处理器排队为止(间歇问题)。

事实上,我们看到这样一个情况:Interval正在执行时,第三个Interval函数将释放。这表明一个重要的事实:Interval对当前正在执行什么漠不关心,它们将不会青红皂白的排队,即使是牺牲回调函数之间的时间也在所不辞。

最后,在第二个Interval函数执行完毕之后,我们可以看到没有留下任何Javascript引擎执行的东西。也就是说,浏览器在等待一个新的异步事件的出现。Interval再次释放时,我们在50ms处获得它,但这一次,执行起来没有任何障碍,它立即释放。

我们来看一个例子,以便更好的说明setTimeout和setInterval的差异:

Select All
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在如何执行异步代码方面有根本的区别;
如果计时器无法立即执行,它将延时到下一个可能的时间执行(这比预想的延迟时间要长一些);
如果有充分的执行时间,Interval可能会毫无延迟的来回执行。
所有这些无疑是重要的知识,了解Javascript引擎如何工作,特别是有大量异步事件出现时,这使得在构建高级应用代码片段时有一个良好的基础。

2、计时器最小延时时间和可靠性

很明显,你可以延迟几秒钟、几分钟、几小时或任何你你想要的时间间隔,但最不明显的是你能选择的最小延时时间。

在一定程度上,浏览器不能为计时器提供良好的解决方案用以精确的处理它们(因为它们自身受操作系统时间的限制)。但是,纵观所有的浏览器,可以很安全的说,最小延时时间大约是10-15ms。

我们可以对跨平台假定的计时器间歇作简单的分析后得出这一结论。例如,如果我们分析延迟时间为0ms的setInterval,我们会发现在大多数浏览器中的最小延迟时间。

在OS操作系统下的浏览器中:

从左上角开始,依次为:Firefox 2, Safari 3, Firefox 3, Opera 9



在Windows操作系统下得浏览器中:

从左上角开始依次为:Firefox 2, Internet Explorer 6, Firefox 3, Opera 9
setTimeout(function(){
/* Some long block of code... */
setTimeout(arguments.callee, 10);
}, 10);
setInterval(function(){
/* Some long block of code... */
}, 10);



上面图表中的线条和数字显示了浏览器同时处理时间间歇的数量,我们可以得出结论:在OS上,浏览器的最小延时时间为10ms,在windows上为15ms。我们可以通过为计时器提供0(或任何10ms以下的任何数值)作为延时时间得到这个值。

但 有一个例外,IE为setInterval提供德尔延时时间不能为0(即使setTimeou能欣然的接受)。当setInterval的延时时间为0 时,它会转变成setTimeout(仅执行一次回调函数),而我们可以通过为其提供1ms的延迟来解决这个问题。由于所有浏览器都能自动向上舍入任何低 于最小延时时间的值,所以用1ms与有效的使用0ms一样安全,或更安全(既然IE浏览器现在能工作)。

从这些表中我们可以得到其它信 息。最重要的是加强了我们以前所了解到的:浏览器不能保证你所指定的精确的时间间歇。像Firefox 2,Opera 9(OS)在提供可靠的执行率方面有一定的难度。很多与浏览器如何处理Javascript的垃圾回收有关(Firefox 3在Javascript的执行上作了显著的改善,其垃圾回收在这些结果中立竿见影)。

因此,浏览器可以提供非常小的延迟时间,但其精确度得不到保证,那么在使用计时器时,你需要考虑你的应用程序(如果10ms和15ms有差异,你应该重新思考你应用程序代码的结构)。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!