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

其他回答

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

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

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

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

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

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

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

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

你的解决方案是:

setTimeout(callback, 0)

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

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

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


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