我最近遇到了一个相当严重的错误,其中代码通过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()?


当前回答

//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');}; }

其他回答

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

看看这个:setTimeout

在问题中,存在以下之间的竞争条件:

浏览器尝试初始化下拉列表,准备更新其选择的索引 设置所选索引的代码

您的代码总是在这场竞赛中胜出,并试图在浏览器准备好之前设置下拉选择,这意味着会出现错误。

这种竞争的存在是因为JavaScript有一个与页面呈现共享的执行线程。实际上,运行JavaScript会阻塞DOM的更新。

你的解决方案是:

setTimeout(callback, 0)

使用回调调用setTimeout,并将0作为第二个参数,将在尽可能短的延迟之后调度回调异步运行——当选项卡有焦点且JavaScript执行线程不忙时,大约为10ms。

因此,OP的解决方案是将所选索引的设置延迟约10ms。这让浏览器有机会初始化DOM,修复bug。

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


请参阅Philip Roberts的“到底什么是事件循环?”以获得更详细的解释。

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

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

问题是您试图在一个不存在的元素上执行Javascript操作。元素还没有被加载,setTimeout()给了一个元素更多的时间,以以下方式加载:

setTimeout() causes the event to be ansynchronous therefore being executed after all the synchronous code, giving your element more time to load. Asynchronous callbacks like the callback in setTimeout() are placed in the event queue and put on the stack by the event loop after the stack of synchronous code is empty. The value 0 for ms as a second argument in function setTimeout() is often slightly higher (4-10ms depending on browser). This slightly higher time needed for executing the setTimeout() callbacks is caused by the amount of 'ticks' (where a tick is pushing a callback on the stack if stack is empty) of the event loop. Because of performance and battery life reasons the amount of ticks in the event loop are restricted to a certain amount less than 1000 times per second.