我最近遇到了一个相当严重的错误,其中代码通过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()?
这里有相互矛盾的点赞答案,没有证据,就无法知道该相信谁。这里证明@DVK是正确的,@SalvadorDali是不正确的。后者声称:
这就是为什么:它不可能有一个时间setTimeout
延迟0毫秒。最小值由
而不是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
在问题中,存在以下之间的竞争条件:
浏览器尝试初始化下拉列表,准备更新其选择的索引
设置所选索引的代码
您的代码总是在这场竞赛中胜出,并试图在浏览器准备好之前设置下拉选择,这意味着会出现错误。
这种竞争的存在是因为JavaScript有一个与页面呈现共享的执行线程。实际上,运行JavaScript会阻塞DOM的更新。
你的解决方案是:
setTimeout(callback, 0)
使用回调调用setTimeout,并将0作为第二个参数,将在尽可能短的延迟之后调度回调异步运行——当选项卡有焦点且JavaScript执行线程不忙时,大约为10ms。
因此,OP的解决方案是将所选索引的设置延迟约10ms。这让浏览器有机会初始化DOM,修复bug。
每个版本的ie浏览器都表现出古怪的行为,这种变通方法有时是必要的。或者,它可能是OP代码库中的一个真正的错误。
请参阅Philip Roberts的“到底什么是事件循环?”以获得更详细的解释。