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


872

最近,我遇到了一个非常讨厌的错误,该错误中的代码是<select>通过JavaScript动态加载的。动态加载的<select>具有预先选择的值。在IE6中,我们已经有代码来修复selected <option>,因为有时<select>selectedIndex值可能与selected <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
大多数问题描述了它为什么有用。如果您需要知道为什么会发生-请阅读我的答案:stackoverflow.com/a/23747597/1090562
萨尔瓦多·达利

18
菲利普·罗伯茨(Philip Roberts)在其演讲“事件循环到底是什么?”中以最好的方式对此进行了解释。youtube.com/watch?v=8aGhZQkoFbQ
vasa 2015年

如果您很着急,这是视频的一部分,他将开始确切地解决这个问题:youtu.be/8aGhZQkoFbQ?t=14m54s。毫无顾忌,整个视频值得一看。
威廉·汉普

4
setTimeout(fn)setTimeout(fn, 0),顺便说一句。
vsync

Answers:


827

在问题中,存在以下竞争条件

  1. 浏览器尝试初始化下拉列表,准备对其选定的索引进行更新,以及
  2. 您的代码来设置选定的索引

您的代码始终在这场比赛中取胜,并在浏览器就绪之前尝试设置下拉菜单,这意味着该错误将出现。

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

您的解决方法是:

setTimeout(callback, 0)

调用setTimeout一个回调,以及零作为第二个参数将安排回调运行异步,最短的延迟之后-这将是10毫秒左右,当标签具有焦点和执行JavaScript的线程不是忙。

因此,OP的解决方案是将选定索引的设置延迟大约10ms。这为浏览器提供了初始化DOM的机会,从而修复了该错误。

Internet Explorer的每个版本都表现出古怪的行为,因此有时需要这种解决方法。另外,它可能是OP代码库中的真正错误。


参见Philip Roberts的演讲“事件循环到底是什么?” 以获得更详尽的解释。


276
“解决方案是“暂停” JavaScript执行,以使渲染线程赶上来。” 并非完全正确,setTimeout所做的是将新事件添加到浏览器事件队列中,并且呈现引擎已经在该队列中(不是完全正确,但足够接近),因此它将在setTimeout事件之前执行。
David Mulder 2012年

48
是的,这是一个更详细,更正确的答案。但是我的“足够正确”,人们可以理解该技巧为何起作用。
staticsan

2
@DavidMulder,这是否意味着浏览器解析CSS并在与JavaScript执行线程不同的线程中进行渲染?
杰森

8
不会,原则上它们是在同一线程中进行解析的,否则,几行DOM操作将始终触发重排,这将对执行速度产生极其不利的影响。
David Mulder

30
此视频是为什么我们的setTimeout了最好的诠释0 2014.jsconf.eu/speakers/...
davibq

665

前言:

其他一些答案是正确的,但实际上并未说明要解决的问题是什么,因此我创建了此答案以提供详细说明。

因此,我将详细介绍浏览器的功能以及如何使用setTimeout()helps。它看起来很长,但实际上非常简单明了-我只是非常详细地介绍了它。

更新:我做了一个JSFiddle来现场演示下面的说明:http : //jsfiddle.net/C2YBE/31/。非常感谢 @ThangChung帮助启动了它。

UPDATE2:以防万一JSFiddle网站死亡或删除代码,我在最后将代码添加到了此答案中。


细节

想象一个带有“执行某项操作”按钮和一个结果div的Web应用程序。

onClick“执行某事”按钮的处理程序调用函数“ LongCalc()”,该函数执行以下两项操作:

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

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

现在,您的用户开始对此进行测试,单击“执行某事”按钮,页面坐在那里似乎在3分钟内什么都没做,他们变得不安,再次单击该按钮,等待1分钟,什么也没有发生,再次单击按钮...

问题很明显-您需要一个“状态” DIV,以显示正在发生的事情。让我们看看它是如何工作的。


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

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

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

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

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

而且,您很乐意将该应用程序提供给用户进行重新测试。

他们回来时看起来很生气。并说明一下,当他们单击按钮时,状态DIV从未更新为“正在计算...”状态!


您挠头,在StackOverflow上询问(或阅读文档或Google),并意识到问题所在:

浏览器将事件产生的所有“ TODO”任务(UI任务和JavaScript命令)都放在一个队列中。不幸的是,用新的“正在计算...”值重新绘制“状态” DIV是一个单独的TODO,它将到达队列的结尾!

这是用户测试期间事件的细分,每个事件之后的队列内容:

  • 队列: [Empty]
  • 事件:单击按钮。事件发生后排队:[Execute OnClick handler(lines 1-4)]
  • 事件:在OnClick处理程序中执行第一行(例如,更改Status DIV值)。事件后排队:[Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value]请注意,虽然DOM更改是瞬时发生的,但要重新绘制相应的DOM元素,您需要一个由DOM更改触发的新事件,该事件发生在队列末尾
  • 问题!!! 问题!!!详细说明如下。
  • 事件:在处理程序中执行第二行(计算)。在之后排队:[Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value]
  • 事件:在处理程序中执行第三行(填充结果DIV)。在之后排队:[Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result]
  • 事件:在处理程序中执行第4行(用“ DONE”填充状态DIV)。队列:[Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value]
  • 事件:returnonclick处理程序子执行隐式执行。我们将“ Execute OnClick处理程序”从队列中移开,然后开始执行队列中的下一项。
  • 注意:由于我们已经完成了计算,因此用户已经过去了3分钟。重画事件尚未发生!!!
  • 事件:使用“计算”​​值重新绘制状态DIV。我们进行重画并将其移出队列。
  • 事件:使用结果值重新绘制结果DIV。我们进行重画并将其移出队列。
  • 事件:使用“完成”值重新绘制状态DIV。我们进行重画并将其移出队列。眼神敏锐的观众甚至可能会注意到“状态DIV带有“计算”值的闪烁几分之一秒- 计算完成后

因此,潜在的问题是,“状态” DIV的重绘事件被放置在队列的末尾,而“执行第2行”事件则需要3分钟,因此实际的重绘不会发生计算完成后。


救援来了setTimeout()。它有什么帮助?因为通过通过调用了长时间执行的代码setTimeout,您实际上创建了2个事件:setTimeout执行本身和(由于0超时)分别为要执行的代码创建队列条目。

因此,要解决您的问题,请将您的onClick处理程序修改为两个语句(在新函数中或中的一个块onClick):

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

  2. 执行setTimeout()0超时并调用LongCalc()function

    LongCalc()功能与上次几乎相同,但显然第一步没有“正在计算...”状态DIV更新;而是立即开始计算。

那么,事件序列和队列现在看起来像什么?

  • 队列: [Empty]
  • 事件:单击按钮。事件发生后排队:[Execute OnClick handler(status update, setTimeout() call)]
  • 事件:在OnClick处理程序中执行第一行(例如,更改Status DIV值)。事件后排队:[Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value]
  • 事件:在处理程序中执行第二行(setTimeout调用)。在之后排队:[re-draw Status DIV with "Calculating" value]。该队列在0秒内没有任何新内容。
  • 事件:0秒后,超时警报将关闭。在之后排队:[re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)]
  • 事件:使用“计算”​​值重新绘制状态DIV。在之后排队:[execute LongCalc (lines 1-3)]。请注意,此重画事件实际上可能在警报响起之前发生,效果也一样。
  • ...

万岁!在开始计算之前,状态DIV已更新为“正在计算...”!



下面是来自JSFiddle的示例代码,这些示例说明了这些示例:http : //jsfiddle.net/C2YBE/31/

HTML代码:

<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好答案!以下是说明您的示例的要点gist.github.com/kumikoda/5552511#file-timeout-html
kumikoda 2013年

4
答案很酷,DVK。为了让您容易想象,我已将该代码放入jsfiddle jsfiddle.net/thangchung/LVAaV
thangchung 2013年

4
@ThangChung-我试图在JSFiddle中制作一个更好的版本(2个按钮,每种情况一个)。由于某些原因,它可以在Chrome和IE上作为演示,但不能在FF上进行演示-请参阅jsfiddle.net/C2YBE/31。我问为什么FF不在这里工作:stackoverflow.com/questions/20747591/...
DVK

1
@DVK“浏览器将事件产生的所有“ TODO”任务(UI任务和JavaScript命令)都放在一个队列中”。主席先生,您能否提供此消息的来源?Imho arent浏览器应该具有不同的UI(呈现引擎)和JS线程....没有冒犯的意图....只是想学习..
bhavya_w 2014年

1
@bhavya_w不,一切都发生在一个线程上。这就是长时间进行js计算会阻塞UI的原因
Seba Kerckhof 2015年



23

大多数浏览器都有一个称为主线程的进程,该进程负责执行一些JavaScript任务,UI更新,例如:绘画,重绘或重排等。

一些JavaScript执行和UI更新任务被排队到浏览器消息队列中,然后被分派到浏览器主线程来执行。

当在主线程繁忙时生成UI更新时,任务将添加到消息队列中。

setTimeout(fn, 0);将其添加fn到要执行的队列的末尾。它计划在给定的时间后将任务添加到消息队列中。


“每个JavaScript执行和UI更新任务都添加到浏览器事件队列系统中,然后将这些任务分派到浏览器主UI线程中执行。”
bhavya_w 2014年

4
高性能JavaScript(Nicholas Zakas,Stoyan Stefanov,Ross Harmes,Julien Lecomte和Matt Sweeney)
Arley 2014年

为此投票add this fn to the end of the queue。最重要的是在setTimeout此功能的确切位置,此循环周期的末尾或下一个循环周期的开始。
绿色

19

这里有一些相互矛盾的,被否决的答案,没有证据就无法知道该相信谁。这证明@DVK是正确的,而@SalvadorDali是不正确的。后者声称:

“这就是为什么:setTimeout的延迟时间为0毫秒是不可能的。最小值是由浏览器确定的,而不是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

您的回答不能证明任何事情。它只是表明在特定情况下计算机上的计算机上会抛出一些数字。要证明相关的内容,您需要的不仅仅是几行代码和一些数字。
萨尔瓦多·达利

6
@SalvadorDali,我相信我的证据足以让大多数人理解。我认为您感到防御并且没有努力去理解它。我很乐于尝试澄清它,但是我不知道您没有理解什么。如果您怀疑我的结果,请尝试在自己的计算机上运行代码。
弗拉基米尔·科纳

我将尝试在最后一次澄清。我的答案是澄清超时,间隔中的一些有趣属性,并得到有效且可识别的来源的充分支持。您强烈声称这是错误的,并指出了由于某种原因而命名为证明的代码段。您的代码没有错(我不反对)。我只是说我的答案没有错。它阐明了一些要点。我要停止这场战争,因为我看不到一点。
萨尔瓦多·达利

15

这样做的一个原因是将代码的执行推迟到一个单独的后续事件循环中。响应某种浏览器事件(例如,鼠标单击)时,有时仅处理当前事件之后才需要执行操作。该setTimeout()设施是最简单的方法。

编辑现在到2015年,我应该注意,还有requestAnimationFrame(),它并不完全相同,但是它非常接近setTimeout(fn, 0),值得一提。


这正是我看到它被使用的地方之一。=)
Zaibot 2011年

仅供参考:此答案已从stackoverflow.com/questions/4574940/…
Shog9 2013年

2
是将代码的执行推迟到一个单独的后续事件循环中:如何计算后续事件循环?您如何确定当前事件循环是什么?您如何知道自己现在处于哪个事件循环?
绿色,

@格林恩,你不是,真的;实际上没有直接了解JavaScript运行时的功能。
尖尖的2015年

requestAnimationFrame解决了我在IE和Firefox中有时无法更新UI的问题
David

9

这是一个有旧答案的老问题。我想对这个问题进行重新审视,并回答为什么会发生这种情况,而不是为什么这样做有用。

因此,您有两个功能:

var f1 = function () {    
   setTimeout(function(){
      console.log("f1", "First function call...");
   }, 0);
};

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

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

这就是为什么:setTimeout延迟时间不可能为0毫秒。的最小值是由浏览器确定并且不为0毫秒。历史上,浏览器将此最小值设置为10毫秒,但是HTML5规范和现代浏览器将其设置为4毫秒。

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

同样来自mozilla:

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

阅读以下文章后即获得PS信息。


1
@ user2407309你在开玩笑吗?您的意思是HTML5规范是错误的,您是正确的吗?拒绝投票并提出强烈要求之前,请先阅读源代码。我的答案基于HTML规范和历史记录。我没有做一遍又一遍地解释完全相同的内容的答案,而是添加了一些新内容,而以前的答案中未显示这些内容。我并不是说这是唯一的原因,我只是在展示一些新东西。
萨尔瓦多·达利

1
这是不正确的:“这就是原因:不可能使setTimeout的延迟时间为0毫秒。” 那不是为什么。4ms延迟与为什么setTimeout(fn,0)有用无关。
弗拉基米尔·科纳

@ user2407309可以很容易地修改为“增加其他人陈述的原因,这是不可能的……”。因此,仅仅因为这个原因而拒绝投票是荒谬的,特别是如果您自己的答案没有告诉任何新的消息。只需进行少量编辑即可。
萨尔瓦多·达利

10
萨尔瓦多·达利(Salvador Dali):如果您在这里忽略微火焰战争的情感方面,您可能不得不承认@VladimirKornea是正确的。的确,浏览器将0ms的延迟映射为4ms,但是即使它们没有映射,结果仍然是相同的。这里的驱动机制是将代码压入队列,而不是调用堆栈。看看这个出色的JSConf演示文稿,它可能有助于澄清问题:youtube.com/watch?
v=8aGhZQkoFbQ

1
对于您为什么认为合格的最小4毫秒报价是全局最小4毫秒,我感到困惑。正如您对HTML5规范的引用所显示的那样,只有当您嵌套的调用深度超过setTimeout/ setInterval超过五个级别时,最小时间才是4毫秒;如果没有,则最小值为0 ms(缺少时间机器的情况下)。Mozilla的文档对此进行了扩展,以涵盖重复的情况,而不仅仅是嵌套的情况(因此setInterval,间隔为0时将立即重新安排几次,然后再延迟更长的时间),但是setTimeout允许使用最小嵌套的简单用法立即排队。
ShadowRanger


7

这两个评价最高的答案都是错误的。查看并发模型和事件循环上的MDN描述,它应该清楚发生了什么(该MDN资源是真正的宝石)。并简单地使用 setTimeout可添加在除了你的代码意想不到的问题,以“解决”这个小问题。

到底什么回事是不是“浏览器可能没有完全准备好,因为并发性”,或基于什么“每一行是被添加到队列的后面的事件”。

的jsfiddleDVK提供确实说明了一个问题,但是他对此的解释不正确。

他的代码中发生的事情是他首先将事件处理程序附加到按钮click上的事件#do

然后,当您实际单击该按钮时,将message引用事件处理函数创建一个,该函数添加到中message queue。当event loop到达此消息时,它将frame在堆栈上创建一个,并在jsfiddle中调用click事件处理程序。

这就是它变得有趣的地方。我们习惯于将Javascript视为异步的,因此我们容易忽略了这个小小的事实:在执行下一帧之前,必须完全执行任何框架。没有并发的人。

这是什么意思?这意味着无论何时从消息队列中调用一个函数,它都会阻塞该队列,直到其生成的堆栈被清空为止。或者,更笼统地说,它阻塞直到函数返回。它阻止了一切,包括DOM渲染操作,滚动和其他功能。如果要确认,只需尝试增加小提琴中长时间运行的操作的持续时间(例如,再运行10次外循环),您会注意到,在运行时,您无法滚动页面。如果运行时间足够长,您的浏览器会询问您是否要终止该进程,因为这会使页面无响应。该框架正在执行,并且事件循环和消息队列一直停留到完成。

那么,为什么文本的这种副作用没有更新?因为尽管你已经改变了DOM元素的值-你可以console.log()它的值后立即改变它,看到它已经被改变(这说明了为什么DVK的解释是不正确的) -浏览器正在等待堆耗尽(on返回的处理函数)并因此完成消息,以便最终可以执行由运行时添加的消息,作为对我们的变异操作的反应,并在UI中反映该变异。

这是因为我们实际上正在等待代码完成运行。我们没有说过“有人先获取它,然后用结果调用此函数,谢谢,现在我完成了imma return,现在就做任何事情”,就像我们通常使用基于事件的异步Javascript一样。我们输入一个click事件处理函数,我们更新一个DOM元素,调用另一个函数,另一个函数工作很长时间,然后返回,然后更新相同的DOM元素,然后从初始函数返回,有效清空堆栈。而随后该浏览器可以得到队列中的下一条消息,这可能是由“上-DOM突变”型事件触发一些内部很可能是由美国产生的消息。

在当前执行的框架完成(函数返回)之前,浏览器UI无法(或选择不更新)UI。就我个人而言,我认为这是设计使然而不是限制。

setTimeout事情为什么起作用呢?这样做是因为它有效地从其自身的框架中删除了对长时间运行的函数的调用,将其调度为在window上下文中稍后执行,因此它本身可以立即返回并允许消息队列处理其他消息。想法是,在更改DOM中的文本时,由我们在Javascript中触发的UI“更新”消息现在位于为长时间运行的功能排队的消息之前,因此UI更新发生在我们阻止之前需很长时间。

请注意,a)长时间运行的函数在运行时仍会阻止所有操作,并且b)您不能保证UI更新实际上在消息队列中排在它前面。在我2018年6月的Chrome浏览器上,值0不能“解决”小提琴演示的问题— 10可以解决。实际上,我对此感到有点窒息,因​​为在我看来,应该将UI更新消息先排队,因为它的触发器是在将长时间运行的函数调度为“稍后”运行之前执行的。但是也许V8引擎中有一些优化可能会干扰,或者也许我缺乏理解。

好的,那么使用问题setTimeout是什么,对于这种特殊情况有什么更好的解决方案?

首先,setTimeout在这样的事件处理程序上使用以尝试缓解另一个问题的问题很容易与其他代码混淆。这是我工作中的一个真实示例:

一位对事件循环有误解的同事试图通过使用一些模板渲染代码setTimeout 0来渲染Java 脚本。他不再在这里问,但是我可以假设他插入了计时器以评估渲染速度(这将是函数的返回即时性),并发现使用此方法可以使该函数快速响应。

第一个问题很明显;您无法线程化javascript,因此在添加混淆时,您在这里一无所获。其次,您现在已经有效地从可能的事件侦听器堆栈中分离了模板的呈现,这些事件侦听器可能希望已经呈现了非常好的模板,而实际上可能还没有。现在,该功能的实际行为是不确定的,就像在不知不觉中一样,任何会运行该功能或依赖该功能的功能都是不确定的。您可以进行有根据的猜测,但不能对其行为进行适当的编码。

编写依赖于其逻辑的新事件处理程序时的“修复” 也要使用setTimeout 0。但是,这不是一个解决办法,很难理解,调试由此类代码引起的错误也没有意思。有时永远不会有问题,有时它总是会失败,然后又一次,有时它会工作并且偶尔会崩溃,这取决于平台的当前性能以及当时发生的任何其他情况。这就是为什么我个人会建议不要使用此hack(这 hack,我们都应该知道),除非您真的知道自己在做什么以及后果是什么。

但是,什么可以我们做的呢?嗯,正如所引用的MDN文章所建议的那样,或者将工作拆分为多个消息(如果可以),以便排队的其他消息可以与您的工作交错并在运行时执行,或者使用可以运行的Web Worker与您的网页并列,并在完成计算后返回结果。

哦,如果您在想,“好吧,我不能只在长时间运行的函数中放置回调以使其异步吗?”,然后否。回调并不会使其异步,它仍必须在明确调用回调之前运行长时间运行的代码。


3

这样做的另一件事是将函数调用推到堆栈的底部,以防递归调用函数时防止堆栈溢出。这具有while循环的效果,但可以让JavaScript引擎触发其他异步计时器。


为此投票push the function invocation to the bottom of the stack。什么stack,你说的是模糊的。最重要的是在setTimeout此功能的确切位置,此循环周期的末尾或下一个循环周期的开始。
格林

1

通过调用setTimeout,您可以给页面时间以响应用户的操作。这对于页面加载期间运行的功能特别有用。


1

setTimeout有用的其他一些情况:

您希望将长时间运行的循环或计算分解为较小的组件,以使浏览器看起来不会“冻结”或说“页面上的脚本忙”。

您希望在单击时禁用表单提交按钮,但是如果在onClick处理程序中禁用该按钮,则不会提交表单。时间为零的setTimeout可以解决问题,它可以使事件结束,可以开始提交表单,然后可以禁用按钮。


2
最好在onsubmit事件中禁用它;这样会更快,并且可以确保在技术上提交表单之前调用它,因为您可以停止提交。
克里斯,

非常真实 我猜想onclick禁用更易于制作原型,因为您可以在按钮中键入onclick =“ this.disabled = true”,而禁用Submit则需要更多的工作。
fabspro 2014年

1

关于执行循环和在其他一些代码完成之前呈现DOM的答案是正确的。JavaScript中的零秒超时有助于使代码成为伪多线程的,即使事实并非如此。

我想补充一点,JavaScript中跨浏览器/跨平台零秒超时的BEST值实际上是20毫秒而不是0(零),因为由于时钟限制,许多移动浏览器无法注册小于20毫秒的超时在AMD芯片上。

另外,不涉及DOM操作的长期运行的进程应立即发送给Web Worker,因为它们提供了真正的JavaScript多线程执行。


2
我对您的回答持怀疑态度,但对此表示反对,因为它迫使我对浏览器标准进行了其他研究。在研究标准时,我会一直走下去,MDN:developer.mozilla.org/en-US/docs/Web/API/window.setTimeout HTML5规范说4毫秒。它没有说明移动芯片的时钟限制。艰难地寻找信息源来备份您的陈述。确实没有找到Google的Dart语言,而是完全删除了setTimeout,以支持Timer对象。
约翰·扎布罗斯基

8
(...),因为很多移动浏览器不能注册超时超过20毫秒,由于时钟限制(......)更小的每个平台都有时间限制,由于它的时钟和没有平台能够执行下一个的东西正是后0MS当前的一个。0ms的超时要求尽快执行功能并且特定平台的时序限制不会以任何方式改变其含义。
Piotr Dobrogost

1

setTimout在0上也很有用,它用于设置要立即返回的延迟承诺:

myObject.prototype.myMethodDeferred = function() {
    var deferredObject = $.Deferred();
    var that = this;  // Because setTimeout won't work right with this
    setTimeout(function() { 
        return myMethodActualWork.call(that, deferredObject);
    }, 0);
    return deferredObject.promise();
}

1

问题是您试图对不存在的元素执行Javascript操作。元素尚未加载,并setTimeout()通过以下方式为元素加载提供了更多时间:

  1. setTimeout()导致事件是异步的,因此在所有同步代码之后执行该事件,从而使您的元素有更多的加载时间。异步回调(如callback in)setTimeout()被放置在事件队列中,并在同步代码堆栈为空之后通过事件循环放入堆栈。
  2. ms的值0作为函数中的第二个参数setTimeout()通常稍高一些(4-10ms,具体取决于浏览器)。执行setTimeout()回调所需的时间稍长,这是由事件循环的“滴答声”量引起的(滴答声将堆栈中的回调推入堆栈,如果堆栈为空)。由于性能和电池寿命原因,事件循环中的滴答声数量限制为每秒少于 1000次的特定数量。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.