为什么setTimeout(fn, 0)有时是有用的?

1089

最近我遇到了一个很棘手的bug,代码通过JavaScript动态加载了一个<select>。这个动态加载的<select>有一个预选值。在IE6中,我们已经编写了代码来修复所选<option>,因为有时<select>selectedIndex值与所选<option>index属性不同步,如下所示:

field.selectedIndex = element.index;

然而,这段代码并没有起作用。尽管字段的 selectedIndex 被正确设置,但最终选择的是错误的索引。不过,如果我在适当的时间添加一个 alert() 语句,那么将会选择正确的选项。考虑到这可能是某种时序问题,我试了一些之前见过的随意尝试:

var wrapFn = (function() {
    var myField = field;
    var myElement = element;

    return function() {
        myField.selectedIndex = myElement.index;
    }
})();
setTimeout(wrapFn, 0);

这个解决方法可行!

我已经找到了我的问题的解决方案,但我不确定为什么这样能够解决我的问题。是否有官方的解释?通过使用setTimeout()并将函数“延迟”调用,我避免了什么浏览器问题?


2
大多数问题描述的是为什么它有用。如果你需要知道为什么会发生这种情况,请阅读我的回答:https://dev59.com/-nRA5IYBdhLWcg3w8SaJ#23747597 - Salvador Dali
32
Philip Roberts在他的演讲"What the heck is the event loop?"中最为生动地解释了这一点。您可以在此链接中观看:https://www.youtube.com/watch?v=8aGhZQkoFbQ - vasa
2
如果你很着急,这是视频中他开始准确回答问题的部分:https://youtu.be/8aGhZQkoFbQ?t=14m54s。无论如何,整个视频都值得一看。 - Kyle Krzeski
7
setTimeout(fn)setTimeout(fn, 0) 是相同的,顺便说一句。 - vsync
与此问题相关的是稍后引入的queueMicrotask()方法,该方法可在HTML DOM API微任务指南中找到。 - papo
19个回答

973

在该问题中,存在一个竞争条件

  1. 浏览器试图初始化下拉列表,准备更新其选定的索引,并且
  2. 您的代码设置了选定的索引。

您的代码一直赢得这场比赛,并在浏览器准备就绪之前尝试设置下拉选择,这意味着错误会出现。

这种竞争存在是因为JavaScript拥有单个执行线程,并与页面渲染共享。实际上,运行JavaScript会阻止DOM的更新。

您的解决方法是:

setTimeout(callback, 0)

使用回调函数并将第二个参数设置为零来调用setTimeout函数将会异步地安排回调函数在可能的最短延迟之后运行,当标签页处于焦点状态且JavaScript执行线程不忙时,这个延迟通常是约10毫秒。

因此,该问题的解决方案是在设置选定索引之前延迟大约10毫秒。这使得浏览器有机会初始化DOM并修复该错误。

每个版本的Internet Explorer都存在一些奇怪的行为,因此有时需要采用这种解决方法。或者它可能是OP代码库中的真正错误。


请参阅Philip Roberts的演讲"What the heck is the event loop?"以获取更详细的说明。


316
解决方案是“暂停”JavaScript执行,以便让渲染线程赶上进度。不完全正确,setTimeout的作用是向浏览器事件队列中添加一个新事件,而渲染引擎已经在该队列中(不完全正确,但足够接近),因此它会在setTimeout事件之前被执行。 - David Mulder
54
是的,那是一个更加详细、更加正确的答案。但我的回答足够“正确”,让人们理解这个技巧是如何起作用的。 - staticsan
2
@DavidMulder,这是否意味着浏览器在不同的线程中解析CSS并呈现,与JavaScript执行线程分开? - jason
8
不,原则上它们在同一线程中解析,否则几行DOM操作将会触发重排,从而极大地影响执行速度。 - David Mulder
32
这个视频是关于为什么我们要使用setTimeout 0的最佳解释,链接为http://2014.jsconf.eu/speakers/philip-roberts-what-the-heck-is-the-event-loop-anyway.html - davibq
显示剩余4条评论

725

前言:

其他答案中有一些是正确的,但并未说明解决的问题是什么,因此我创建了这个答案来呈现详细的说明。

因此,我发布了一个详细的演示浏览器工作原理以及如何使用setTimeout()。看起来很长,但实际上非常简单明了 - 我只是让它变得非常详细。

更新: 我已经制作了一个JSFiddle来实时演示下面的说明: http://jsfiddle.net/C2YBE/31/ 。非常感谢@ThangChung提供帮助。

更新2: 以防万一JSFiddle网站死亡或删除代码,我在本答案的最后添加了代码。


详情:

想象一个带有“做某事”按钮和结果div的Web应用程序。

“做某事”按钮的onClick 处理程序调用一个名为"LongCalc()"的函数,该函数执行以下两项任务:

  1. 进行非常长的计算(例如需要3分钟)

  2. 将计算结果打印到结果div中。

现在,您的用户开始测试它,单击“做某事”按钮,然后页面似乎无所作为地等待3分钟,他们变得不耐烦,再次单击按钮,等待1分钟,没有任何反应,再次单击按钮……

问题很明显 - 您希望有一个“状态”DIV,显示正在进行的操作。让我们看看它是如何工作的。


因此,您添加了一个“状态”DIV(最初为空),并修改了onclick处理程序(函数LongCalc())以执行4个操作:

  1. 将状态“正在计算...可能需要~3分钟”填充到状态DIV中

  2. 进行非常长的计算(例如需要3分钟)

  3. 将计算结果打印到结果div中。

  4. 将状态“计算完成”填充到状态DIV中

然后,您愉快地将应用程序交给用户进行重新测试。

他们生气地回到您这里,并解释说当他们单击该按钮时,状态DIV从未更新为“正在计算...”状态!!!


您摇了摇头,向StackOverflow询问(或阅读文档或搜索),并意识到问题:

浏览器将所有UI任务和JavaScript命令产生的“待办事项”放入单个队列中。不幸的是,使用新的“Calculating…”值重新绘制“Status” DIV是一个单独的待办事项,它会排在队列的末尾!
以下是用户测试期间事件的详细信息以及每个事件后队列的内容:
- 队列:[空] - 事件:单击按钮。事件后的队列:[执行OnClick处理程序(第1至4行)] - 事件:执行OnClick处理程序的第一行(例如更改状态DIV值)。事件后的队列:[执行OnClick处理程序(第2至4行),使用新的“Calculating”值重新绘制状态DIV]。请注意,虽然DOM更改是即时发生的,但为了重新绘制相应的DOM元素,您需要一个新的事件,该事件由DOM更改触发,并在队列末尾。 - 问题!问题!下面解释了详情。 - 事件:执行处理程序的第二行(计算)。队列后:[执行OnClick处理程序(第3至4行),使用“Calculating”值重新绘制状态DIV]。 - 事件:执行处理程序的第三行(填充结果DIV)。队列后:[执行OnClick处理程序(第4行),使用结果填充结果DIV;使用结果填充结果DIV,使用结果填充结果DIV]。 - 事件:执行处理程序的第四行(使用“DONE”填充状态DIV)。队列:[执行OnClick处理程序,使用“Calculating”值重新绘制状态DIV,使用结果填充结果DIV;使用“Done”值重新绘制状态DIV]。 - 事件:执行OnClick处理程序子程序中的暗示返回。我们从队列中取出“执行OnClick处理程序”,并开始执行队列中的下一项。 - 注意:由于我们已经完成了计算,因此用户用时已经过去了3分钟。重新绘制事件还没有发生! - 事件:使用"Calculating"值重新绘制状态DIV。我们进行重新绘制并将其从队列中删除。 - 事件:使用结果值重新绘制结果DIV。我们进行重新绘制并将其从队列中删除。 - 事件:使用“Done”值重新绘制状态DIV。我们进行重新绘制并将其从队列中删除。敏锐的观察者甚至可能会注意到状态DIV与“正在计算”的值在计算结束后闪烁了几微秒。
因此,根本问题在于“Status” DIV的重新绘制事件被放在队列的末尾,在执行需要3分钟的“执行第二行”事件之后,实际的重新绘制不会发生直到计算完成后。使用 setTimeout() 函数可以帮助解决问题。为什么呢?因为通过使用setTimeout 调用长时间执行的代码,你实际上创建了 2 个事件: setTimeout 的执行本身,以及(由于0超时),被执行代码的单独队列条目。
所以,为了解决您的问题,在新函数中或在 onClick 中的块中将您的 onClick 处理程序修改为两个语句:
1. 将“正在计算...可能需要~3分钟”状态填充到状态 DIV 中。 2. 使用 0 超时和对 LongCalc() 函数的调用来执行 setTimeout()
那么现在事件序列和队列是什么样的呢?
队列: [空] 事件:单击按钮。 事件后的队列:[执行 OnClick 处理程序(状态更新, setTimeout() 调用)] 事件:执行 OnClick 处理程序的第一行(例如更改 Status DIV 值)。事件后的队列:[执行 OnClick 处理程序(它是一个 setTimeout 调用), 重新绘制包含新“计算”的值的 Status DIV]。 事件:执行处理程序的第二行(setTimeout 调用)。之后的队列:[重新绘制包含“正在计算”的值的 Status DIV]。队列在0秒内没有任何新内容。
时间:从超时警报开始,0 秒后。队列后:[重新绘制包含“正在计算”的值的 Status DIV,执行 LongCalc(第1-3行)]。 事件:重新绘制包含“正在计算”的值的 Status DIV。之后的队列:[执行 LongCalc(第1-3行)]。请注意,此重绘事件实际上可能发生在警报响起之前,这同样有效。
太好了!计算开始前,状态 DIV 刚刚更新为“正在计算...”!
以下是 JSFiddle 中说明这些示例的示例代码:http://jsfiddle.net/C2YBE/31/
<table border=1>
    <tr><td><button id='do'>Do long calc - bad status!</button></td>
        <td><div id='status'>Not Calculating yet.</div></td>
    </tr>
    <tr><td><button id='do_ok'>Do long calc - good status!</button></td>
        <td><div id='status_ok'>Not Calculating yet.</div></td>
    </tr>
</table>

JavaScript代码:(在onDomReady上执行,可能需要jQuery 1.9)

function long_running(status_div) {

    var result = 0;
    // Use 1000/700/300 limits in Chrome, 
    //    300/100/100 in IE8, 
    //    1000/500/200 in FireFox
    // I have no idea why identical runtimes fail on diff browsers.
    for (var i = 0; i < 1000; i++) {
        for (var j = 0; j < 700; j++) {
            for (var k = 0; k < 300; k++) {
                result = result + i + j + k;
            }
        }
    }
    $(status_div).text('calculation done');
}

// Assign events to buttons
$('#do').on('click', function () {
    $('#status').text('calculating....');
    long_running('#status');
});

$('#do_ok').on('click', function () {
    $('#status_ok').text('calculating....');
    // This works on IE8. Works in Chrome
    // Does NOT work in FireFox 25 with timeout =0 or =1
    // DOES work in FF if you change timeout from 0 to 500
    window.setTimeout(function (){ long_running('#status_ok') }, 0);
});

12
感谢您的称赞,DVK! 这里有一个要点概括,用来说明您的例子:https://gist.github.com/kumikoda/5552511#file-timeout-html - kumikoda
4
非常棒的回答,DVK。为了更容易理解,我把那段代码放在了 jsfiddle 上,链接为 http://jsfiddle.net/thangchung/LVAaV/ 。 - thangchung
4
@ThangChung - 我试图在 JSFiddle 中制作一个更好的版本(2 个按钮,每种情况一个),它在 Chrome 和 IE 上作为演示工作,但由于某种原因在 FF 上不起作用-请参见http://jsfiddle.net/C2YBE/31/。我问了为什么FF在这里不起作用:https://dev59.com/2XrZa4cB1Zd3GeqP4JNE - DVK
1
@DVK:“浏览器将所有由事件产生的“TODO”任务(包括UI任务和JavaScript命令)都放置在一个队列中。”先生,您能提供这个说法的来源吗?在我看来,浏览器不应该有不同的UI(渲染引擎)和JS线程吗……没有冒犯的意思……只是想学习一下…… - bhavya_w
1
@DVK:这是一个非常好的解释,但有一件事我没有理解。而且不幸的是,使用新的“Calculating…”值重新绘制“Status” DIV是一个单独的TODO,它被放到了队列的末尾!为什么要放在队列的末尾?为什么不放在开头或者写它的地方?把它放在队列的末尾有什么特别的原因吗? - Razort4x
显示剩余22条评论

93

请查看约翰·雷西格(John Resig)关于JavaScript计时器工作原理的文章。当您设置一个超时时,它实际上会将异步代码排队,直到引擎执行当前调用堆栈。


29

setTimeout()函数可以让你暂缓执行,直到DOM元素被加载完毕,即使延时设置为0。

查看这个链接:setTimeout


26
这里有一些冲突的赞同回答,没有证据就无法知道应该相信谁。这是@DVK正确而@SalvadorDali不正确的证明。后者声称:
“原因在于:不能设置延迟为0毫秒的setTimeout。最小值由浏览器确定,不是0毫秒。历史上,浏览器将此最小值设置为10毫秒,但HTML5规范和现代浏览器将其设置为4毫秒。”
4ms最小超时与正在发生的事情无关。真正发生的是setTimeout将回调函数推到执行队列的末尾。如果在setTimeout(callback,0)之后您有阻塞代码需要运行几秒钟,那么回调将在几秒钟后才被执行,直到阻塞代码完成。尝试这段代码:
function testSettimeout0 () {
    var startTime = new Date().getTime()
    console.log('setting timeout 0 callback at ' +sinceStart())
    setTimeout(function(){
        console.log('in timeout callback at ' +sinceStart())
    }, 0)
    console.log('starting blocking loop at ' +sinceStart())
    while (sinceStart() < 3000) {
        continue
    }
    console.log('blocking loop ended at ' +sinceStart())
    return // functions below
    function sinceStart () {
        return new Date().getTime() - startTime
    } // sinceStart
} // testSettimeout0

输出为:

setting timeout 0 callback at 0
starting blocking loop at 5
blocking loop ended at 3000
in timeout callback at 3033

你的回答并不能证明什么。它只是表明在你的机器上,在特定情况下,计算机给你一些数字。要证明相关的事情,你需要比几行代码和几个数字更多的东西。 - Salvador Dali
7
@SalvadorDali,我认为我的证明已经很清楚,大多数人都能理解。我觉得你有些防御心态,没有努力去理解它。我很乐意尝试澄清,但我不知道你没有理解的地方。如果你怀疑我的结果,请在你的机器上运行代码。 - Val Kornea

26
浏览器有一个称为“主线程”的进程,负责执行一些JavaScript任务、UI更新等:绘画、重绘、回流等。JavaScript任务被排队到消息队列中,然后被分派到浏览器的主线程中执行。当主线程忙碌时生成UI更新时,任务将被添加到消息队列中。

每个JavaScript执行和UI更新任务都会被添加到浏览器事件队列系统中,然后这些任务会被分派到浏览器主UI线程进行执行。...来源是什么? - bhavya_w
4
《高性能JavaScript》视频(作者:Nicholas Zakas, Stoyan Stefanov, Ross Harmes, Julien Lecomte和Matt Sweeney) - Arley
对于这个“将此函数添加到队列末尾”的操作进行点踩。最重要的是,setTimeout 函数会在循环周期的末尾添加该函数,还是在下一个循环周期的开始添加该函数。 - Green

21

如果你不想看整个视频,以下是一个简单的解释,让你能够理解这个问题的答案:

  1. JavaScript是单线程,也就是说它在运行时只能做一件事情。
  2. 但是JavaScript运行的环境可以是多线程的。例如,浏览器通常是多线程的,即能够同时处理多个任务。因此,它们可以同时运行JavaScript并跟踪处理其他任务。

从这个点开始,我们谈论的是“在浏览器中的JavaScript”。像setTimeout这样的东西确实属于浏览器范畴,而不是JavaScript本身的一部分。

  1. 允许JavaScript异步运行的是多线程浏览器! 除了JavaScript使用的主要空间(称为调用栈)以便将每行代码放置在上面,并逐行运行,浏览器还为JavaScript提供了另一个空间来存储和运行代码。

现在让我们称该空间为第二个空间

  1. 假设fn是一个函数。 在这里需要理解的重要一点是,fn();调用与setTimeout(fn, 0);调用不相等,下面将进一步解释。

我们先假设延迟时间不为0,比如5000毫秒:setTimeout(fn, 5000);。需要注意的是这仍然是一个“函数调用”,因此必须放在主空间中,并在完成后从中删除,但等等!我们不喜欢整整5秒钟的长时间延迟。那会阻塞主空间,而且不允许JavaScript同时运行其他任何东西。

幸运的是,这不是浏览器设计者设计它们工作方式的方式。相反,这个调用(setTimeout(fn, 5000);)立即完成。这非常重要:即使延迟了5000毫秒,此函数调用也会在瞬间完成!接下来会发生什么?它从主空间中删除。它将被放在哪里?(因为我们不想失去它)。你可能已经猜到了:浏览器“听到”这个调用,并将其放在第二个空间中。 enter image description here

浏览器会记录5秒的延迟,一旦时间过去,它会查看主空间,并且在“空闲时”将fn();函数调用放回主空间。这就是setTimeout的工作原理。
因此,回到setTimeout(fn, 0),即使延迟为零,这仍然是对浏览器的调用,浏览器立即响应并将其放置在第二个空间,并仅在主空间再次为空闲时将其放回主空间,而不是真正的0毫秒后。
我强烈推荐您观看该视频,因为他解释得非常好,并且更加深入地介绍了技术内容。

https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#run-steps-after-a-timeout - Monday Fatigue

20
这两个最受欢迎的答案都是错误的。可以查看MDN关于并发模型和事件循环的描述,就可以清楚地了解发生了什么(该MDN资源是真正的宝石)。而且,仅仅使用setTimeout可能会在您的代码中添加意外问题,而不仅仅是“解决”这个小问题。
实际上发生的情况并不是“浏览器可能还没有准备好,因为并发性”,或者基于“每行是添加到队列末尾的事件”之类的东西。
DVK提供的jsfiddle确实说明了一个问题,但他的解释是不正确的。
他代码中发生的情况是,首先将一个事件处理程序附加到#do按钮的click事件上。
然后,当你实际点击按钮时,将创建一个引用事件处理函数的message,该函数将添加到message queue。当event loop达到此消息时,它会在堆栈上创建一个frame,其中包含对jsfiddle中单击事件处理程序的调用。
这就是有趣的地方。我们习惯于认为JavaScript是异步的,以至于容易忽略这一点:任何frame在下一个frame执行之前都必须完全执行。没有并发,各位。
这是什么意思?这意味着每当从消息队列调用函数时,它都会阻塞队列,直到生成的堆栈被清空为止。或者更一般地说,它会阻塞直到函数返回。它会阻塞所有操作,包括DOM渲染操作、滚动等等。如果您想确认,只需尝试增加fiddle中长时间运行操作的持续时间(例如,再运行外部循环10次),您会注意到在其运行时,您无法滚动页面。如果它运行足够长时间,您的浏览器将询问您是否要终止该进程,因为它使页面无响应。帧正在执行,并且事件循环和消息队列被卡住,直到它完成。
那么为什么文本不更新的副作用会出现?因为虽然您已经在DOM中更改了元素的值——您可以立即在更改后使用console.log()查看其值并查看它已经被更改了(这表明DVK的解释是不正确的)——但浏览器正在等待堆栈耗尽(on处理程序函数返回),因此消息才能完成,以便最终能够执行由运行时作为对我们突变操作的反应添加的消息,并反映该突变在UI中。
这是因为我们实际上在等待代码运行完成。与我们通常使用的基于事件的异步Javascript不同,我们没有说“请获取这个并使用结果调用此函数,谢谢,现在我完成了所以我要返回,现在做任何事情”。我们进入一个点击事件处理程序函数,更新DOM元素,调用另一个函数,另一个函数工作很长时间然后返回,然后我们更新相同的DOM元素,然后我们从最初的函数返回,有效地清空堆栈。接着,浏览器就可以继续处理队列中的下一条消息,可能正是我们通过触发某些内部的“on-DOM-mutation”类型事件生成的消息。
浏览器UI不能(或选择不)更新UI,直到当前执行的帧已经完成(函数已经返回)。个人认为这更多是设计而不是限制。
那么,为什么setTimeout会起作用呢?因为它有效地将对长时间运行函数的调用从自己的帧中移除,在window上下文中安排它稍后执行,以便它本身可以立即返回并允许消息队列处理其他消息。而这个想法是,我们在Javascript中触发更改DOM文本时触发的UI“更新”消息现在在排队等待长时间运行函数的消息之前,因此UI更新在我们阻塞很长时间之前发生。
请注意,a) 当长时间运行的函数运行时仍会阻塞一切,b) 您不能保证 UI 更新实际上在消息队列中位于其前面。在我的 2018 年 6 月版 Chrome 浏览器上,值为 0 不会“修复”演示中 fiddle 所展示的问题,而值为 10 才能解决。我有点受到限制,因为我认为 UI 更新消息应该在其之前排队,因为它的触发器在安排运行“稍后”长时间运行的函数之前执行。但可能 V8 引擎中存在一些优化可能会干扰,或者我的理解不够深入。

好吧,那么在事件处理程序中使用 setTimeout 存在什么问题?对于这种情况,有更好的解决方案吗?

首先,使用像这样的 setTimeout 在任何事件处理程序上尝试消除另一个问题的问题容易干扰其他代码。以下是我工作中的一个现实例子:

一位同事,在误解事件循环的情况下,尝试通过使某些模板渲染代码使用setTimeout 0进行其渲染。他已经不在这里了,所以我可以推测他可能插入了计时器来衡量渲染速度(即函数的返回即时性),并发现使用这种方法可以使该函数的响应速度飞快。

第一个问题很明显,你不能线程化JavaScript,因此当你添加混淆时,并没有获得任何好处。其次,你现在已经有效地将模板的渲染与可能期望该模板已被渲染的可能事件侦听器的堆栈分离开来,而实际上可能并没有被渲染。该函数的实际行为现在是不确定的,就像运行它或依赖它的任何函数一样。你可以做出合理的猜测,但你不能正确地编写其行为的代码。
在编写一个新的事件处理程序依赖于其逻辑时,“修复”方法是同时使用setTimeout 0。但这不是一种解决方法,很难理解,而且调试由这样的代码引起的错误也不好玩。有时没有任何问题,有时会持续失败,然后又有时它会工作并间歇性地崩溃,这取决于平台的当前性能和其他正在进行的事情。这就是为什么我个人建议不要使用这个hack(它是一个hack,我们都应该知道它是),除非你真的知道自己在做什么和后果是什么。
那么,我们应该做些什么呢?正如MDN文章所建议的那样,要么将工作分成多个消息(如果可以),以便其他排队的消息可以与你的工作交错执行,要么使用Web Worker,在页面上并行运行并在计算完成后返回结果。

哦,如果你在想,“那我不能在长时间运行的函数中放一个回调来使它异步吗?”,不行。回调并不能让它异步,它仍然必须在显式调用回调之前运行长时间运行的代码。


3
看起来这是整个页面上唯一有效且完整的评论。 - rottweilers_anonymous
1
讲解得非常清楚,谢谢! - Noob

17

其中一个原因是将代码执行推迟到单独的、随后的事件循环中。当响应某种浏览器事件(例如鼠标点击)时,有时需要在当前事件被处理之后才执行操作。 setTimeout() 是最简单的方法。

编辑 现在已经是2015年了,我应该指出还有requestAnimationFrame(),虽然它不完全相同,但与setTimeout(fn, 0)足够接近,值得一提。


这正是我看到它被使用的地方之一。=) - Zaibot
FYI:此答案已从http://stackoverflow.com/questions/4574940/settimeout-with-zero-delay-used-often-in-web-pages-why合并到此处。 - Shog9
2
延迟代码执行到单独的后续事件循环中:你如何确定“后续事件循环”?你如何确定当前事件循环是什么?你如何知道你现在处于哪个事件循环中? - Green
1
@Green,实际上你不行;因为没有直接的可视性来查看 JavaScript 运行时正在进行的操作。 - Pointy
requestAnimationFrame解决了我在IE和Firefox中有时无法更新UI的问题。 - David

13
这是一个旧问题,有一些旧的回答。我想要从新的角度来看待这个问题,并回答为什么会发生这种情况,而不是为什么这有用。
所以你有两个函数:
var f1 = function () {    
   setTimeout(function(){
      console.log("f1", "First function call...");
   }, 0);
};

var f2 = function () {
    console.log("f2", "Second call...");
};

然后按照以下顺序调用它们 f1(); f2(); 只是为了看到第二个函数先执行。

原因在于:不可能使用0毫秒的时间延迟来使用setTimeout。最小值由浏览器确定,而且不是0毫秒。 历史上 浏览器将此最小值设置为10毫秒,但HTML5规范和现代浏览器将其设置为4毫秒。

如果嵌套级别大于5,并且超时小于4,则将超时增加到4。

还有来自Mozilla的信息:

要在现代浏览器中实现0毫秒超时,可以使用window.postMessage(),如此处所述。

P.S. 阅读以下文章后获得信息。


1
@user2407309 你在开玩笑吗?你的意思是HTML5规范是错误的,而你是正确的?在你给出负评和强烈声明之前,请先阅读来源。我的回答基于HTML规范和历史记录。我不是在一遍又一遍地解释完全相同的东西,而是添加了一些新东西,一些以前的答案中没有显示过的东西。我并不是说这是唯一的原因,我只是展示一些新的东西。 - Salvador Dali
1
这是不正确的:“原因在于:不可能使用0毫秒的时间延迟来执行setTimeout。”但事实并非如此。4毫秒的延迟与setTimeout(fn,0)有用的原因无关。 - Val Kornea
@user2407309 这可以很容易地修改为“除了其他人提到的原因之外,不可能......”。因此,如果您自己的答案没有说出任何新内容,仅因此而投反对票是荒谬的。只需要进行一些小的编辑就可以了。 - Salvador Dali
12
萨尔瓦多·达利(Salvador Dali)说:如果你忽略了这场微小的情感战,你可能不得不承认@VladimirKornea是正确的。的确,浏览器将0毫秒延迟映射为4毫秒,但即使它们没有这样做,结果仍将是相同的。这里的驱动机制是将代码推送到队列中,而不是调用堆栈。看一下这个优秀的JSConf演示文稿,它可能有助于澄清问题:https://www.youtube.com/watch?v=8aGhZQkoFbQ - hashchange
2
我对你认为4毫秒的合格最小值是全局最小值感到困惑。正如你从HTML5规范中引用的那样,只有在嵌套调用setTimeout/setInterval超过五层时,最小值才为4毫秒;如果没有,最小值为0毫秒(因为缺乏时间机器)。Mozilla的文档将其扩展到重复而不仅仅是嵌套的情况(因此,间隔为0的setInterval会立即重新安排几次,然后在此之后延迟更长时间),但允许最简单的使用setTimeout进行最小嵌套。 - ShadowRanger

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