递归的setTimeout和setInterval有什么区别?

808
就我所知,这两段 JavaScript 代码的行为是相同的:
选项 A:
function myTimeoutFunction()
{
    doStuff();
    setTimeout(myTimeoutFunction, 1000);
}

myTimeoutFunction();

选项B:
function myTimeoutFunction()
{
    doStuff();
}

myTimeoutFunction();
setInterval(myTimeoutFunction, 1000);

使用setTimeoutsetInterval之间有什么区别吗?

6
如果您需要了解JS计时器的工作原理细节,John Resig在这个主题上撰写了一篇很好的文章 - Rafael
9
明显的差异在于setTimeout需要额外的代码行来保持其运行,这带来了维护问题的缺点,但也有让您轻松更改时间间隔的好处。 - annakata
请尝试访问此链接:http://jsfiddle.net/pramendra/Y4vEV/ - Pramendra Gupta
4
谢谢@JapanPro,但我从来没有真正遇到过超时问题。这篇文章是关于它们之间的区别和应该使用哪个的。 - Damovisa
更新链接。编辑队列已满。 setTimeoutsetInterval 的区别。 - Tropen
20个回答

709

它们本质上尝试实现相同的目标,但setInterval方法比setTimeout方法更准确,因为setTimeout等待1000毫秒,然后运行函数并设置另一个超时。因此,等待时间实际上比1000毫秒长一点(如果您的函数执行时间很长,则会更长)。

虽然人们可能认为setInterval精确地每1000毫秒执行一次,但重要的是要注意,setInterval也会有延迟,因为JavaScript不是多线程语言,这意味着-如果脚本中还有其他部分在运行-间隔必须等待其完成。

这个演示中,您可以清楚地看到超时会落后,而间隔几乎一直以接近每秒1次的速度工作(这正是脚本试图实现的目标)。如果你将顶部的速度变量改为像20这样的小值(表示它将尝试每秒运行50次),间隔定时器永远无法达到平均每秒50次迭代。

延迟几乎总是可以忽略不计的,但如果您正在编写真正精确的程序,则应该选择自适应定时器(它本质上是一种基于超时的定时器,不断调整其为创建的延迟)。


4
安迪提出了类似的建议。假设,这是否意味着如果该方法执行时间超过1000毫秒,您可以同时运行多个实例? - Damovisa
14
理论上是可以的。但在实践中不行,因为Javascript不支持多线程。如果你的代码执行时间超过1000毫秒,它会使浏览器卡住。 - Kamiel Wanrooij
62
从技术上讲,代码不会确切地每隔1000毫秒执行一次,因为它取决于定时器的分辨率和其他代码是否已经在执行。尽管如此,你的观点仍然正确。 - Matthew Crumley
10
setInterval 有两个不同之处:1. setInterval 不是递归的,而且 setInterval 会在指定时间后第一次调用您的函数,而 setTimeout 第一次被调用时没有任何延迟,之后才按照指定时间调用。在首次执行后,它们几乎工作相同。 - Hafiz
5
setTimeout和setInterval的区别在于,setTimeout不会重复执行。它允许你在设定的时间后运行一次脚本,而且只运行一次。另一方面,setInterval会重复执行该脚本,直到使用clearTimeout()停止为止。 - Leander
显示剩余2条评论

664

有什么区别吗?

有。Timeout会在setTimeout()调用一定时间后执行;而Interval会在上一个interval触发一定时间后执行。

如果您的doStuff()函数需要执行一段时间,您将会注意到它们之间的区别。例如,如果我们用.表示setTimeout/setInterval的调用,用*表示timeout/interval的触发,用[-----]表示JavaScript代码执行,则时间线如下所示:

Timeout:

.    *  .    *  .    *  .    *  .
     [--]    [--]    [--]    [--]

Interval:

.    *    *    *    *    *    *
     [--] [--] [--] [--] [--] [--]

下一个问题是如果在 JavaScript 已经忙于处理先前的间隔(例如处理上一个间隔)时,一个间隔触发了。在这种情况下,该间隔将被记住,并在前一个处理程序完成并将控制返回到浏览器后立即发生。因此,例如对于一个有时很短([-])有时很长([-----])的 doStuff() 过程:

.    *    *    •    *    •    *    *
     [-]  [-----][-][-----][-][-]  [-]

• 表示一个间隔触发器不能立即执行其代码,而是被挂起。

因此,间隔触发器尝试“赶上”以重新回到预定时间表。但是,它们不会在彼此之上排队:每个间隔触发器一次只能有一个等待执行的任务。(如果它们都排队等候,浏览器将面临一个不断扩大的未完成任务列表!)

.    *    •    •    x    •    •    x
     [------][------][------][------]

x代表一个间隔触发器,它无法执行或未能被置为挂起状态,因此被丢弃。

如果您的doStuff()函数通常需要执行的时间比设置的间隔时间长,那么浏览器将会占用100%的CPU尝试服务它,并可能变得不太响应。

你使用哪个并说明原因?

Chained-Timeout会给浏览器留出一个保证的空闲时间槽;Interval试图确保正在运行的功能在其计划的时间尽可能接近地执行,以牺牲浏览器UI的可用性。

如果我想要流畅度尽可能高的一次性动画,我会考虑使用Interval,而对于在页面加载时一直进行的持续动画,则使用链接的超时更加礼貌。对于不太苛刻的用途(例如每30秒触发一次的无足轻重的更新程序),您可以安全地使用任何一个。

就浏览器兼容性而言,setTimeout早于setInterval,但是今天所有的浏览器都支持这两种方法。多年来最后的落伍者是WinMo <6.5中的IE Mobile,但希望这也已经过去了。


3
一种优雅的做法是使用匿名函数进行链式定时器操作:setTimeout(function(){ setTimeout(arguments.callee,10) },10) - Justin Meyer
1
就浏览器兼容性而言,虽然所有浏览器都提供这两种方法,但它们的性能是不同的。 - unigg
1
通过Chromium项目的鼓机(http://code.google.com/p/chromium/source/browse/trunk/samples/audio/shiny-drum-machine.html?r=2738#656),使用setTimeout("schedule()", 0)。这样,当Chrome浏览器处于后台标签或缺乏资源时,浏览器就不会有不必要的堆栈。 - Elliot Yap
当你说间隔被丢弃时...你是指在那个特定的时间内...如果我有100个间隔...它们都会在某个时候运行。丢弃只是意味着一些间隔会比原计划晚一些运行? - user656925
@bobince 你写道: "下一个复杂情况是,如果一个间隔在 JavaScript 已经忙于处理某些事情(比如处理之前的间隔)时触发。在这种情况下,间隔会被记住,并在前一个处理程序完成并将控制返回给浏览器后立即发生。" 我注意到这主要发生在你离开浏览器或标签时。有没有解决这个问题的方法?或者有没有一些在纯 JS 中实现自己的间隔的例子? - undefined
显示剩余3条评论

98

setInterval()

setInterval() 是一个基于时间间隔的代码执行方法,具有在达到时间间隔时重复运行指定脚本的本地能力。作者不应该将它嵌套到回调函数中以使其循环,因为它默认循环。除非您调用 clearInterval(),否则它将保持按照间隔时间间隔触发。

如果您想为动画或时钟滴答声循环代码,请使用 setInterval()

function doStuff() {
    alert("run your code here when time interval is reached");
}
var myTimer = setInterval(doStuff, 5000);

setTimeout()

setTimeout() 是一种基于时间的代码执行方法,当达到指定时间间隔时,它将仅执行一次脚本。除非你将 setTimeout() 对象嵌套在调用运行的函数内以循环执行脚本,否则它将不会再次重复执行。如果设置为循环执行,它将会在间隔时间内不断触发,除非你调用 clearTimeout() 来清除。

function doStuff() {
    alert("run your code here when time interval is reached");
}
var myTimer = setTimeout(doStuff, 5000);
如果您想让某些事情在指定的时间后发生一次,那么请使用setTimeout()。这是因为它只会在达到指定的时间间隔时执行一次。

7
解释不错,但请注意原帖作者已经了解他所提供的示例中的基本区别。特别是请注意,在原帖作者的setTimeout()示例中,setTimeout()递归调用,而setInterval()则没有。 - DavidRR
1
最好的解释。 - Rahul

45

使用 setInterval 可以更方便地取消代码的未来执行。如果使用 setTimeout,您必须在以后希望取消时跟踪计时器 ID。

var timerId = null;
function myTimeoutFunction()
{
    doStuff();
    timerId = setTimeout(myTimeoutFunction, 1000);
}

myTimeoutFunction();

// later on...
clearTimeout(timerId);

对比

function myTimeoutFunction()
{
    doStuff();
}

myTimeoutFunction();
var timerId = setInterval(myTimeoutFunction, 1000);

// later on...
clearInterval(timerId);

35
我不明白为什么在setInterval中,你没有追踪计时器的id。它只是从函数中抽离出来了。 - Kekoa
1
使用 setTimeout 的另一个选项是,不保存 ID,而是添加一个 if 语句,只有在某个条件为真时才设置下一个超时。 - nnnnnn
7
Kekoa,说得不错。 但是你只需要保存间隔ID一次。 超时ID可能会在每次调用中发生更改,因此您必须“跟踪”这些更改。 想要清除超时的代码必须以某种方式访问该变量的当前值。 此外,在运行“doStuff”时无法清除超时,因为ID无效。 然而,没有必要保存超时ID。 相反,您可以停止调用“setTimeout”。 - Robert
4
根据我的经验,大多数情况下即使在使用setTimeout时你也希望可以取消。99%的情况下,你想在下一次调用发生之前取消,而不是在下一次调用完成之后取消。 - Kamiel Wanrooij

23

如果您想要取消超时,我发现setTimeout方法更易于使用:

function myTimeoutFunction() {
   doStuff();
   if (stillrunning) {
      setTimeout(myTimeoutFunction, 1000);
   }
}

myTimeoutFunction();

此外,如果函数出现错误,它会在第一次错误处停止重复,而不是每秒重复出错。


21

它们的区别在于它们的目的不同。

setInterval()
   -> executes a function, over and over again, at specified time intervals  

setTimeout()
   -> executes a function, once, after waiting a specified number of milliseconds

就是这么简单。

更详细的信息请查看http://javascript.info/tutorial/settimeout-setinterval


7
简洁明了的解释,但请注意,原帖作者已经理解提供的示例之间的基本区别。特别需要注意的是,在原帖作者提供的setTimeout()示例中,setTimeout()递归调用,而setInterval()则不是。 - DavidRR

16

当你在setInterval内运行某些函数时,如果这个函数的执行时间超过了timeout,浏览器将会卡住。

- 例如,doStuff()需要1500秒才能执行,你这样写:setInterval(doStuff, 1000);
1) 浏览器运行 doStuff(),需要1.5秒才能执行;
2) 大约1秒后,它尝试再次运行 doStuff()。但是先前的 doStuff()仍在执行,所以浏览器将此运行添加到队列中(等待第一个完成后运行)。
3,4,…)同样将下一次迭代的执行添加到队列中,但是先前的 doStuff()仍在进行中…
结果-浏览器被卡住了。

为了防止这种情况发生,最好的方法是在setTimeout内运行 setTimeout来模拟setInterval
为了纠正setTimeout调用之间的超时,可以使用JavaScript的setInterval的自我修正替代技术


2
我不知道为什么没有人发现这个答案的价值! - Randika Vishman

10

你的代码将有不同的执行间隔,在一些项目中,比如在线游戏中是不可接受的。首先,为了让你的代码在相同的间隔下工作,你应该将 "myTimeoutFunction" 更改为以下内容:

function myTimeoutFunction()
{
    setTimeout(myTimeoutFunction, 1000);
    doStuff();
}
myTimeoutFunction()

在这次改变之后,它将等于

function myTimeoutFunction()
{
    doStuff();
}
myTimeoutFunction();
setInterval(myTimeoutFunction, 1000);

但是,由于JS是单线程的,所以您仍然无法获得稳定的结果。如果JS线程在忙于某些事情,它将无法执行您的回调函数,并且执行将被推迟2-3毫秒。如果您每秒有60次执行,并且每次都有1-3秒的随机延迟,那么这将绝对不可接受(一分钟后将会有大约7200毫秒的延迟),我可以建议使用类似以下的方法:

    function Timer(clb, timeout) {
        this.clb = clb;
        this.timeout = timeout;
        this.stopTimeout = null;
        this.precision = -1;
    }

    Timer.prototype.start = function() {
        var me = this;
        var now = new Date();
        if(me.precision === -1) {
            me.precision = now.getTime();
        }
        me.stopTimeout = setTimeout(function(){
            me.start()
        }, me.precision - now.getTime() + me.timeout);
        me.precision += me.timeout;
        me.clb();
    };

    Timer.prototype.stop = function() {
        clearTimeout(this.stopTimeout);
        this.precision = -1;
    };

    function myTimeoutFunction()
    {
        doStuff();
    }

    var timer = new Timer(myTimeoutFunction, 1000);
    timer.start();

这段代码将保证稳定的执行周期。即使线程繁忙,您的代码将在1005毫秒后执行,下一次会有995毫秒的超时时间,结果将是稳定的。


9

2
是的,我明白这个区别,但是我提供的这两段代码应该做同样的事情... - Damovisa
嗯,是的...我本以为...但根据dcaunt和他的投票数,情况并不完全如此。 - Bravax

7
我对setInterval(func, milisec)进行了简单的测试,因为我想知道当函数耗时大于间隔时间时会发生什么。 setInterval通常会在上一次迭代开始后立即安排下一次迭代,除非函数仍在运行。如果是这样,setInterval将等待函数结束。一旦发生这种情况,函数就会立即再次触发——不会按照计划等待下一次迭代(在没有超时函数的情况下会这样)。也不会出现并行迭代运行的情况。
我在Chrome v23上进行了测试。我希望它在所有现代浏览器中都具有确定性实现。
window.setInterval(function(start) {
    console.log('fired: ' + (new Date().getTime() - start));
    wait();
  }, 1000, new Date().getTime());

控制台输出:

fired: 1000    + ~2500 ajax call -.
fired: 3522    <------------------'
fired: 6032
fired: 8540
fired: 11048

wait函数只是一个线程阻塞辅助工具——同步的AJAX调用,在服务器端需要精确处理2500毫秒

function wait() {
    $.ajax({
        url: "...",
        async: false
    });
}

1
“没有并行迭代运行的情况” - 是的,这应该是不可能的。客户端JavaScript具有单线程执行模型,因此任何事情(事件处理程序等)都不会同时发生。这就是为什么在同步ajax调用期间什么也不会发生(浏览器无响应)。 - MrWhite
在“间隔”之间的几毫秒内,浏览器是否响应?如果有待处理的事件,是否会触发另一个事件?(谢谢你的测试,顺便加一) - MrWhite
1
根据MDN的说法,如果函数执行时间超过间隔时间,则被认为是“危险用法”。递归的setTimout调用是首选。 - MrWhite

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接