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

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

问题描述:

我最近遇到了一个相当讨厌的错误,其中代码通过 JavaScript 动态加载了 .这个动态加载的 有一个预先选择的值.在 IE6 中,我们已经有代码来修复选定的 ,因为有时 selectedIndex 值会是与选定的 index 属性不同步,如下所示:

I've recently run into a rather nasty bug, wherein the code was loading a <select> dynamically via JavaScript. This dynamically loaded <select> had a pre-selected value. In IE6, we already had code to fix the selected <option>, because sometimes the <select>'s selectedIndex value would be out of sync with the selected <option>'s index attribute, as below:

field.selectedIndex = element.index;

但是,此代码不起作用.即使该字段的 selectedIndex 设置正确,最终还是会选择错误的索引.但是,如果我在正确的时间插入 alert() 语句,则会选择正确的选项.考虑到这可能是某种时间问题,我尝试了一些我之前在代码中看到的随机内容:

However, this code wasn't working. Even though the field's selectedIndex was being set correctly, the wrong index would end up being selected. However, if I stuck an alert() statement in at the right time, the correct option would be selected. Thinking this might be some sort of timing issue, I tried something random that I'd seen in code before:

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

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

这奏效了!

我已经找到了解决我的问题的方法,但我很不安,我不知道为什么这会解决我的问题.谁有官方的解释?通过使用 setTimeout()稍后"调用我的函数,我可以避免什么浏览器问题?

I've got a solution for my problem, but I'm uneasy that I don't know exactly why this fixes my problem. Does anyone have an official explanation? What browser issue am I avoiding by calling my function "later" using setTimeout()?

在问题中,存在一个种族条件介于:

In the question, there existed a race condition between:

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

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

Your code was consistently winning this race and attempting to set drop-down selection before the browser was ready, meaning that the bug would appear.

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

This race existed because JavaScript has a single thread of execution that is shared with page rendering. In effect, running JavaScript blocks the updating of the DOM.

您的解决方法是:

setTimeout(callback, 0)

使用回调调用 setTimeout,第二个参数为零将调度回调在最短的可能延迟之后异步运行 - 这将是大约 10 毫秒,当该选项卡具有焦点且 JavaScript 执行线程不忙.

Invoking setTimeout with 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,修复错误.

The OP's solution, therefore was to delay by about 10ms, the setting of the selected index. This gave the browser an opportunity to initialize the DOM, fixing the bug.

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

Every version of Internet Explorer exhibited quirky behaviors and this kind of workaround was necessary at times. Alternatively it might have been a genuine bug in the OP's codebase.

请参阅 Philip Roberts 演讲 事件循环到底是什么?"了解更多信息详尽的解释.

See Philip Roberts talk "What the heck is the event loop?" for more thorough explanation.