关于JavaScript中的setTimeout()链式调用和setInterval()探索
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
关于JavaScript中的setTimeout()链式调用和setInterval()探索的更多相关文章
- JavaScript设计模式——方法的链式调用
方法的链式调用: (function() { //私有类 function _$ (els) { this.elements = []; for(var i = 0, len = els.length ...
- JavaScript设计模式 Item 5 --链式调用
1.什么是链式调用 这个很容易理解,例如: $(this).setStyle('color', 'red').show(); 一般的函数调用和链式调用的区别:调用完方法后,return this返回当 ...
- 在Vue单页面应用中使用Promise链式调用
eg: this.commonLoginFun().then((res) => { if (res.errNo === 0) { const { isLogin } = res.data; if ...
- JavaScript实现链式调用
学习Jquery的时候,我们通常会看到链式调用的写法 $(window).addEvent('load', function(){ $('test').show().setStyle('color', ...
- js实现方法的链式调用
假如这里有三个方法:person.unmerried();person.process();person.married();在jQuery中通常的写法是:person.unmerried().pro ...
- JavaScript中的链式调用
链模式 链模式是一种链式调用的方式,准确来说不属于通常定义的设计模式范畴,但链式调用是一种非常有用的代码构建技巧. 描述 链式调用在JavaScript语言中很常见,如jQuery.Promise等, ...
- javascript学习(10)——[知识储备]链式调用
上次我们简单的说了下单例的用法,这个也是在我们java中比较常见的设计模式. 今天简单说下链式调用,可能有很多人并没有听过链式调用,但是其实只要我简单的说下的话,你肯定基本上都在用,大家熟知的jQue ...
- JavaScript链式调用
1.什么是链式调用? 这个很容易理解,例如 $('text').setStyle('color', 'red').show(); 一般的函数调用和链式调用的区别:链式调用完方法后,return thi ...
- 编程中的链式调用:Scala示例
编程中的链式调用与Linux Shell 中的管道类似.Linux Shell 中的管道 ,会将管道连接的上一个程序的结果, 传递给管道连接的下一个程序作为参数进行处理,依次串联起N个实用程序形成流水 ...
随机推荐
- 《编写高质量代码-Web前端开发修改之道》笔记--第一章 从网站重构说起
本章内容: 糟糕的页面实现,头疼的维护工作 Web标准--结构.样式和行为的分离 前端的现状 打造高品质的前端代码,提高代码的可维护性--精简.重用.有序 糟糕的页面实现,头疼的维护工作 工作中最大的 ...
- SQL Server 2008 R2密钥序列号
SQL Server 2008 R2密钥序列号 序列号: 开发版(Developer): PTTFM-X467G-P7RH2-3Q6CG-4DMYB 企业版(Enterprise): JD8Y6-HQ ...
- Cassandra1.2文档学习(19)—— CQL索引
参考文档:http://www.datastax.com/documentation/cql/3.0/webhelp/index.html#cql/ddl/ddl_primary_index_c.ht ...
- Linux下通过shell脚本创建账户
当我们在linux平台上开发一些项目时,或者有一些项目是需要部署到linux系统上时,有时候会涉及到linux上的特定的账户,例如有一些项目需要运行在某些特定的账户下,或者有时候需要在全新的环境上搭建 ...
- redis参考
www.redis.cn www.redis.io http://blog.nosqlfan.com/ 可以移步http://try.redis.io/进行实验命令 Redis 设计与实现(第一版) ...
- EXTJS 4.2 资料 控件之 Store 用法
最近工作,发现在Extjs中自定义Store的功能挺多,特意在此做笔记,几下来,具体代码如下: 1.定义Store //定义Store var ItemSelectorStore = new Ext. ...
- SVN备份教程(一)
最近一段时间在项目中用到了SVN备份的相关内容,这里给大家做一个简单的教程,重点在于SVN备份环境的搭建过程中,大家学到的解决问题的思维方式. 1.分类 SVN备份主要分为两种:一种是远程备份,另一种 ...
- sql中的inner join, left join, right join的区别
下面介绍一下 inner join, left join, right join这者之间的区别 现在我假设有A表和B表 left join select * from A a left join B ...
- ajax,json和$.each()
json返回的时候,只需要展示部分字段,如果是 ajax从后台获取结果处理,可以使用.select() 等处理结合匿名类,生成需要的字段的匿名类json字符串,返回前端,可以使用$.parseJson ...
- sb 讲解 (!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]]
代码:(!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]] 输出sb. 分段解析: 首先解析s: (! ...