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

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

Sam神乐番长2020/03/10 10:29:28

Javascript是单线程应用程序,因此不允许同时运行功能,因此要使用此事件循环。因此,正是setTimeout(fn,0)所做的事情使它被塞入任务请求中,该任务在您的调用堆栈为空时执行。我知道这个解释很无聊,所以我建议您看这段视频,这将帮助您在浏览器中进行工作。观看以下视频:-https : //www.youtube.com/watch?time_continue=392&v=8aGhZQkoFbQ

Mandy小卤蛋凯2020/03/10 10:29:28

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

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

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

L前端2020/03/10 10:29:28

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

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

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

JinJin卡卡西小胖2020/03/10 10:29:27

setTimeout有用的其他一些情况:

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

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

Itachi猪猪Green2020/03/10 10:29:27

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

乐米亚2020/03/10 10:29:27

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

番长GO2020/03/10 10:29:27

这是一个带有旧答案的旧问题。我想重新看待这个问题,并回答为什么会发生这种情况,而不是为什么这样做很有用。

因此,您有两个功能:

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

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

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

And here is why: it is not possible to have setTimeout with a time delay of 0 milliseconds. The Minimum value is determined by the browser and it is not 0 milliseconds. Historically browsers sets this minimum to 10 milliseconds, but the HTML5 specs and modern browsers have it set at 4 milliseconds.

If nesting level is greater than 5, and timeout is less than 4, then increase timeout to 4.

Also from mozilla:

To implement a 0 ms timeout in a modern browser, you can use window.postMessage() as described here.

P.S. information is taken after reading the following article.

JimAJim2020/03/10 10:29:27

setTimeout() 即使将DOM元素设置为0,也会让您花一些时间直到DOM元素被加载。

检查一下:setTimeout

十三泡芙猿2020/03/10 10:29:27

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

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

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

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

乐小小猪猪2020/03/10 10:29:27

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

前端Tom2020/03/10 10:29:27

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

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

阳光伽罗2020/03/10 10:29:27

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

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

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

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

您的解决方法是:

setTimeout(callback, 0)

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

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

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


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