1001

我最近遇到了一个相当讨厌的错误,其中代码是<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()

4

19 回答 19

914

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

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

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

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

您的解决方法是:

setTimeout(callback, 0)

Invoking setTimeoutwith a callback, and zero as the second argument will schedule the callback to be run asynchronously , after the shortest possible delay - which will be around 10ms when the tab has focus and the JavaScript thread of execution is not busy.

因此,OP 的解决方案是将所选索引的设置延迟大约 10 毫秒。这给了浏览器一个初始化 DOM 的机会,修复了这个 bug。

每个版本的 Internet Explorer 都表现出古怪的行为,这种变通方法有时是必要的。或者,它可能是 OP 代码库中的真正错误。


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

于 2009-04-23T00:14:17.927 回答
703

前言:

其他一些答案是正确的,但实际上并没有说明要解决的问题是什么,所以我创建了这个答案来展示详细的说明。

因此,我发布了有关浏览器功能以及如何使用setTimeout()帮助的详细演练。它看起来很长,但实际上非常简单明了——我只是把它写得很详细。

更新:我制作了一个 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. 将状态“正在计算...可能需要约 3 分钟”填充到状态 DIV

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

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

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

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

他们回到你身边,看起来很生气。并解释当他们单击按钮时,状态 DIV 从未更新为“正在计算...”状态!!!


你挠头,在 StackOverflow 上四处询问(或阅读文档或谷歌),然后意识到问题所在:

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

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

  • 队列:[Empty]
  • 事件:点击按钮。活动后排队:[Execute OnClick handler(lines 1-4)]
  • 事件:在 OnClick 处理程序中执行第一行(例如更改状态 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]
  • 事件:在处理程序中执行第 3 行(填充结果 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]
  • return事件:从onclick处理程序子隐含执行。我们从队列中取出“Execute OnClick 处理程序”并开始执行队列中的下一项。
  • 注意:由于我们已经完成了计算,因此用户已经过了 3 分钟。重画事件还没有发生!!!
  • 事件:使用“计算”​​值重新绘制状态 DIV。我们重新绘制并将其从队列中移除。
  • 事件:使用结果值重新绘制 Result DIV。我们重新绘制并将其从队列中移除。
  • 事件:使用“完成”值重新绘制状态 DIV。我们重新绘制并将其从队列中移除。眼尖的观众甚至可能会注意到“状态 DIV 与“计算”值在几分之一微秒内闪烁 -计算完成后

所以,根本的问题是“状态”DIV的重绘事件被放置在最后的队列中,在“执行第2行”事件之后需要3分钟,所以实际的重绘不会发生,直到计算完成后。


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

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

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

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

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

那么,事件序列和队列现在是什么样子的呢?

  • 队列:[Empty]
  • 事件:点击按钮。活动后排队:[Execute OnClick handler(status update, setTimeout() call)]
  • 事件:在 OnClick 处理程序中执行第一行(例如更改状态 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 代码:(在 jQuery 1.9 上执行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);
});
于 2011-01-01T17:53:17.850 回答
93

查看 John Resig 关于JavaScript 计时器如何工作的文章。当您设置超时时,它实际上将异步代码排队,直到引擎执行当前调用堆栈。

于 2010-12-06T21:38:30.173 回答
26

setTimeout()在加载 DOM 元素之前为您争取一些时间,即使设置为 0。

看看这个:setTimeout

于 2009-04-22T21:52:10.047 回答
25

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

于 2013-02-25T21:27:03.443 回答
23

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

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

4 毫秒的最小超时与正在发生的事情无关。真正发生的是 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
于 2014-07-26T00:30:21.370 回答
17

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

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

于 2011-01-01T17:38:04.523 回答
14

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

这里实际发生的不是“浏览器可能还没有完全准备好,因为并发性”或基于“每一行都是一个添加到队列后面的事件”的东西。

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

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

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

这就是有趣的地方。我们习惯于认为 Javascript 是异步的,以至于我们很容易忽略一个小事实:任何帧都必须在执行下一帧之前完全执行。没有并发,人。

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

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

这是因为我们实际上是在等待代码完成运行。我们还没有说“有人获取这个然后调用这个函数并使用结果,谢谢,现在我已经完成了,所以我马上返回,现在做任何事情”,就像我们通常使用基于事件的异步 Javascript 所做的那样。我们进入一个click事件处理函数,我们更新一个DOM元素,我们调用另一个函数,另一个函数工作了很长时间然后返回,然后我们更新同一个DOM元素,然后我们从初始函数返回,有效清空堆栈。然后浏览器可以获取队列中的下一条消息,这很可能是我们通过触发一些内部“on-DOM-mutation”类型事件生成的消息

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

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

请注意,a) 长时间运行的函数在运行时仍然会阻塞所有内容,并且 b) 您不能保证 UI 更新实际上在消息队列中在它之前。在我 2018 年 6 月的 Chrome 浏览器上,值010 并不能“解决”小提琴演示的问题 - 10 可以。我实际上对此感到有些窒息,因为对我来说,UI 更新消息应该在它之前排队似乎是合乎逻辑的,因为它的触发器是在安排长时间运行的函数“稍后”运行之前执行的。但也许 V8 引擎中的一些优化可能会干扰,或者我的理解可能只是缺乏。

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

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

一位同事对事件循环有错误的理解,试图通过使用一些模板渲染代码来“线程化”Javascript setTimeout 0。他不再来这里问了,但我可以推测,也许他插入了计时器来衡量渲染速度(这将是函数的返回即时性),并发现使用这种方法会使得该函数的响应速度极快。

第一个问题很明显;你不能线程化 javascript,所以当你添加混淆时你在这里什么也得不到。其次,您现在已经有效地将模板的渲染从可能的事件侦听器堆栈中分离出来,这些事件侦听器可能期望该模板已经被渲染,而它很可能没有被渲染。该函数的实际行为现在是非确定性的,就像——在不知不觉中——任何运行它或依赖它的函数一样。您可以做出有根据的猜测,但您无法正确编码其行为。

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

但是我们做些什么呢?好吧,正如引用的 MDN 文章所建议的那样,要么将工作拆分为多条消息(如果可以的话),以便排队的其他消息可以与你的工作交错并在它运行时执行,或者使用可以运行的 web worker与您的页面同步,并在完成计算后返回结果。

哦,如果你在想,“好吧,我不能在长时间运行的函数中放置一个回调以使其异步吗?”然后不。回调不会使其异步,它仍然需要在显式调用回调之前运行长时间运行的代码。

于 2018-06-05T06:26:31.450 回答
13

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

所以你有两个功能:

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

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

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

这就是原因:不可能有setTimeout0 毫秒的时间延迟。最小值由浏览器确定,它不是 0 毫秒。过去浏览器将此最小值设置为 10 毫秒,但HTML5 规范和现代浏览器将其设置为 4 毫秒。

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

同样来自Mozilla:

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

PS信息是在阅读以下文章后获取的。

于 2014-05-19T21:34:47.610 回答
8

由于它的持续时间为0,我想这是为了setTimeout从执行流程中删除传递给 的代码。因此,如果它是一个可能需要一段时间的函数,它不会阻止后续代码执行。

于 2011-01-01T17:39:29.277 回答
3

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

于 2012-06-21T01:14:43.583 回答
3

如果你不想看完整的视频,这里简单解释一下需要了解的内容,以便能够理解这个问题的答案:

  1. JavaScript 是单线程的,这意味着它在运行时一次只做一件事。
  2. 但是运行 JavaScript 的环境可以是多线程的。例如,浏览器通常是多线程的生物,即,一次能够做多项事情。所以他们可以运行 JavaScript,同时也可以跟踪处理其他事情。

从现在开始,我们将讨论“在浏览器中”的 JavaScript。类似setTimeout的东西确实是浏览器的东西,而不是 JavaScript 本身的一部分。

  1. 允许 JavaScript 异步运行的是多线程浏览器!除了 Javascript 使用的主要空间(称为调用堆栈)来放置每一行代码并逐个运行它们之外,浏览器还为 JavaScript 提供了另一个空间来放置东西。

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

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

我们先假设另一个延迟,而不是0延迟,例如 5000 毫秒setTimeout(fn, 5000);:重要的是要注意这仍然是一个“函数调用”,所以它必须放在主空间,并在完成后从其中删除,但是等等!我们不喜欢整个冗长而无聊的 5 秒延迟. 这将阻塞主空间,同时不允许 JavaScript 运行其他任何东西。

谢天谢地,这不是浏览器设计者设计它们的方式。相反,这个 call( setTimeout(fn, 5000);) 是立即完成的。这很重要:即使有 5000 毫秒的延迟,这个函数调用也是在瞬间完成的!接下来会发生什么?它从主空间中删除。会放在哪里?(因为我们不想失去它)。您可能猜对了:浏览器听到这个调用并将其放在第二个空格上。 在此处输入图像描述

浏览器会跟踪 5 秒的延迟,一旦延迟过去,它会查看主空间,然后“当它为空时”,将fn();调用返回给它。这就是setTimeout工作原理。

所以,回到setTimeout(fn, 0),即使延迟为零,这仍然是对浏览器的调用,浏览器会立即听到并拿起它,并将其放在第二个空间,然后再将其放回主空间仅当主空间又是空的,而且不是真正的 0 毫秒后

我真的建议您也观看该视频,因为他的解释非常好,并且更多地打开了技术问题。

于 2021-05-23T07:29:28.537 回答
2

通过调用 setTimeout,您可以让页面有时间对用户所做的任何事情做出反应。这对于在页面加载期间运行的函数特别有用。

于 2009-04-22T21:53:43.553 回答
2

setTimeout 有用的其他一些情况:

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

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

于 2012-12-14T13:09:00.043 回答
2

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

  1. setTimeout()导致事件是异步的,因此在所有同步代码之后执行,从而为您的元素提供更多加载时间。像里面的回调这样的异步回调setTimeout()被放入事件队列中,在同步代码栈为空后由事件循环入栈。
  2. ms 作为函数中的第二个参数的值 0setTimeout()通常略高(4-10ms 取决于浏览器)。执行回调所需的稍长的时间setTimeout()是由事件循环的“滴答声”(如果堆栈为空,滴答声将回调推送到堆栈上)的数量引起的。由于性能和电池寿命的原因,事件循环中的滴答数被限制为每秒少于1000 次的特定数量。
于 2018-09-15T09:12:40.750 回答
1

关于执行循环和在其他代码完成之前渲染 DOM 的答案是正确的。JavaScript 中的零秒超时有助于使代码成为伪多线程,即使它不是。

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

此外,不涉及 DOM 操作的长时间运行的进程现在应该发送到 Web Workers,因为它们提供了 JavaScript 的真正多线程执行。

于 2013-03-13T23:29:55.870 回答
1

setTimout on 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();
}
于 2015-09-02T15:39:58.573 回答
0

//When need "new a", setTimeout(fn, 0) is useful, when need to wait some action. Example:

var a = function (){console.log('a');};
var b = function(){setTimeout(b, 100);}; //wait some action before override this function

//without setTimeout:
console.log('no setTimeout: b.toString():', b.toString());
b();    //"b" is an old function
console.log('no setTieout: a.toString(): ', a.toString());
a();    //and "a" is not overrided

setTimeout(//but with setTimeout(fn, 0):
    function(){
        console.log('After timeout 0, b.toString(): ', b.toString());
        b();    //"b" is a new function
        console.log('After timeout 0, a.toString(): ', a.toString());
        a();    //and "a" is overrided
    },
    0
);

//override var "b", which was been undefined
b = function (){
    a = function(){console.log('new a');};
}

于 2021-07-12T04:48:12.483 回答
-2

Javascript 是单线程应用程序,因此不允许同时运行函数,因此要使用事件循环来实现。那么 setTimeout(fn, 0) 究竟是做什么的,它被推入任务任务中,当您的调用堆栈为空时执行该任务。我知道这个解释很无聊,所以我建议你看一下这个视频,这将帮助你了解浏览器中的工作原理。观看此视频:- https://www.youtube.com/watch?time_continue=392&v=8aGhZQkoFbQ

于 2018-01-22T07:27:14.700 回答