http://www.cnblogs.com/Wenwang/archive/2012/01/06/2314283.html
http://www.cnblogs.com/yangjunhua/archive/2012/04/12/2444106.html
下面的参考:http://evantre.iteye.com/blog/1718777
1、选题缘起
在知乎上瞎逛的时候看到一个自问自答的问题:
知乎上,前端开发领域有哪些值得推荐的问答?,然后在有哪些经典的 Web 前端或者 JavaScript 面试笔试题?中看到这么一题:以下两段代码有什么不同?
(1)
- setTimeout( function() {
- /* 代码块... */
- setTimeout( arguments.callee, 10 );
- }, 10 );
(2)
- setInterval( function() {
- /*代码块... */
- }, 10 );
2、长话短说
对于这个问题本身而言,并不需要花费太多篇幅和时间来说明。简单地说setTimeout()指在指定时间后执行一次指定函数,setInterval()指每隔一段所指定的时间执行一次指定函数,两个方法都称为定时器,不是ECMAScript的内容,而是属于BOM。
0 --x--10,分成3个区间
下面先列出关于这个问题的几个结论:
(1)当其中定时器指定函数所需执行时间几乎为0ms时,setTimeout()链式调用和setInterval()可以说没有什么明显区别,均每间隔10ms开始执行一次代码块中的内容,每次所需时间0ms。(这种情况应该少见,或者是一种理想假定)
(2)当其中定时器指定函数执行所需时间0 < T < 10ms时,二者差别非常明显。假定执行队列中没有其他任务,定时器所指定函数可以在指定间隔时间添加到执行队列并立即执行,假定定时器所指定函数执行时间需要5ms,0ms时开始执行定时器。
那么对于setTimeout()链式调用而言,在10ms,setTimeout()所指定函数被添加到执行队列并立即执行,到15ms,代码块执行完毕,此时又创建一个定时器,那么间隔10ms即在25ms时,函数第二次执行。
而对于setInterval()而言,同样,在10ms时,setTInterval()所指定函数开始执行,在15ms时候,代码块也就是所指定函数执行完毕,然而下一次开始执行函数的时间将是20ms时,是从第一次开始执行的时间算起间隔10ms,而与函数执行所需时间无关(当然这里时间不能大于10ms,前面已经作了假设)。
(3)当其中定时器指定函数执行所需时间 T > 10ms时,二者差别同样非常明显。假定执行队列中没有其他任务,定时器所指定函数可以在指定间隔时间添加到执行队列并立即执行,假定定时器所指定函数执行时间需要20ms,0ms时开始执行定时器。
那么对于setTimeout()链式调用而言,在10ms时,setTimeout()所指定函数被添加到执行队列并立即执行,到30ms,代码块执行完毕,此时又创建一个定时器,那么间隔10ms即在40ms时,函数第二次执行。
而对于setInterval()而言,同样,在10ms时,setInterval()所指定函数开始执行,在30ms时候,代码块也就是所指定函数执行完毕,原本按照指定下一次开始执行函数的时间应该是20ms时,但是这里函数的执行时间已经超过了20ms,那么按指定,下一次执行该函数的时间应该是30ms时,而在此时函数第一次执行刚好完毕,那么就会马上执行第二次,结果就是在第一次执行完毕,到下一次开始执行这个期间几乎没有时间间隔。
这也就是setTimeout()链式调用和setInterval()的区别之处,两种用法本来所表达的就不是一个意思。setTimeout()链式调用所指定的时间间隔是指函数前一次执行结束到后一次执行开始之间的间隔,而setInterval()所指定的时间是指函数前一次开始执行的时间和后一次开始执行的时间之间的间隔。
(4)那么在实际应用中,应该根据你想表达的意思选择合适的方法,比如如果你是想从某个时刻开始,每隔一分钟响铃一次用来提示时间,该任务每次执行时间时间需要50ms,这个时候你就应该使用setInterval(),时间间隔指定为1分钟(自己换成毫秒)。如果这种情况你用setTimeout()链式调用指定相同的时间,那么结果会是第一次响铃时间恰好是0秒的时候,但第二次响铃的时候却是0秒加50ms(因为你是在响铃任务完成后立即添加的又一个setTimeout()定时器),然后结果越来越脱离你的预期。因此我觉得setInterval()更像是一个闹钟,而setTimeout()则是真正的定时器。
(5)按照Nicholas的说法是,一般认为,使用超时调用模拟间歇调用是一种最佳模式,开发环境下,很少使用真正的间歇调用,原因是后一个间歇调用可能会在前一个间歇调用结束前启动,但仅当没有改定时器的任何其他代码事例时,才将定时器代码添加到队列中,结果就可能会跳过一些定时器代码。
3、实践分析
(该段可略过)刚看到这个题目的时候感觉有那么点印象,但是再回忆时却发现记忆并不深刻,这也源于我平时看书不太认真之缘故,因为关于这个问题在Nicholas的《JavaScript高级程序设计(第三版)》一书中是有比较明确的说明的。当时看到这个问题时也是想当然的就Google或者Baidu,确实也检索到很多有价值的信息,比如有的结果就将我引回到Nicholas的书中。相信很多人对这个问题都已经很熟悉,但也可能会有很多新手还会不太明白,于是我打算站在前人的基础对关于setTimeout()和setInterval()的问题进行一下搜集、整理和归纳,也谈谈自己的一些想法。同时在鬼懿IT-成长群:181368696 中谈论这个问题的时候,管理员安也让我将该问题整理成文,于是又有了不少动力。而在一步步实践验证的过程中发现原本二者的差异是非常明显的,这原本就是一个很基础很简单的问题,但既然也费了些时间,就把这个过程记录下来。
1、分析(实验+理论)
setTimeout()和setInterval()都是用来创建定时器,可以实现很多功能。通常情况下,setTimeout()的链式调用和setInterval()都可以让程序在一定的时间间隔内重复执行一段代码。JavaScript是运行于单线程的环境中,只有当进程空闲时才会执行所添加到执行队列中的下一段代码,没有任何代码是可以确保立即执行的。不管是setTimeout()还是setInterval()所指定的时间都只能确保在何时将定时器指定的代码添加到执行队列中,而不是何时实际执行代码,且setTimeout()和setInterval()指定的定时器代码只有在创建它的函数执行完成之后,才有可能被执行。还有一个问题就是定时器所指定的代码所执行的时间可能会大于定时器指定的时间间隔。下面以具体的事例来说明各种情况。
(1)为了进行测试,写了一个延时函数,大致延时139ms,具体如下
- function delay() {
- //此处添加时间是为了测试这个函数会延时多久
- var start = Date.now(),
- end;
- for (var i = 0; i < 10000; i++) {
- for (var j = 0; j < 10000; j++);
- }
- end = Date.now();
- console.log( end - start );
- }
通过下面这段代码进行测试
- for (var i = 0; i < 200; ++i) {
- delay();
- }
(这段代码执行时候会阻塞页面,仅仅用于测试)运行经过多次测试取一段稳定值,延时时间大致平均为139ms(根据具体环境,我是在Chrome22/Win7下运行的)
(2)接下来分别使用setTimeout()链式调用和setInterval()分别来调用这段延时代码,第二个参数均设置为50ms。
首先使用setTimeout()链式调用,代码如下
- function delay() {
- for (var i = 0; i < 10000; i++) {
- for (var j = 0; j < 10000; j++);
- }
- }
- var before_time = Date.now(),
- after_time,
- end;
- setTimeout( function() {
- after_time = Date.now();
- var start = after_time;
- console.log( after_time - before_time );//前一次开始执行到后一次开始执行时间间隔
- console.log( start - end ); //前一次结束到后一次开始之间时间间隔
- before_time = after_time;
- delay();
- end = Date.now();
- setTimeout( arguments.callee, 50 );
- }, 50 );
运行结果如下:
像我们预期的那样,前一次结束到后一次开始之间间隔50ms运行。前一次开始执行到后一次开始执行时间间隔约210ms,基本稍微大于每次的139ms加上间隔的50ms,其中有一些其他开销。
现在使用setInterval()来看一看,代码如下
- function delay() {
- for (var i = 0; i < 10000; i++) {
- for (var j = 0; j < 10000; j++);
- }
- }
- var before_time = Date.now(),
- after_time,
- end;
- setInterval( function() {
- after_time = Date.now();
- var start = after_time;
- console.log( after_time - before_time );//前一次开始执行到后一次开始执行时间间隔
- console.log( start - end ); //前一次结束到后一次开始之间时间间隔
- before_time = after_time;
- delay();
- end = Date.now();
- }, 50 );
运行结果如下
很明显,前一次结束到后一次开始之间几乎没有时间间隔。由于没有间隔,前一次开始执行到后一次开始执行时间间隔140ms,基本等于delay()函数的每次139ms。
(3)接下来分别使用setTimeout()链式调用和setInterval()分别来调用这段延时代码,与上面不同的是第二个参数均设置为200ms。
首先来看看setTimeout()链式调用,代码依然和上面一样,只是时间改为200ms,结果如下
结果不出所料,前一次结束到后一次开始之间间隔200ms运行。前一次开始执行到后一次开始执行时间间隔约355ms,稍微大于200ms+139ms。
下面再来看看setInterval(),结果如下
可以看到前一次开始执行到后一次开始执行时间间隔恰好为指定的200ms,而前一次结束到后一次开始之间间隔稍微小于200ms - 139ms。
参考:
【1】Nicholas,JavaScript高级程序设计(第三版),人民邮电出版社,2012
【2】Nicholas,高性能JavaScript,电子工业出版社,2010