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


当前回答

setTimout on 0在设置一个延迟承诺的模式中也非常有用,你想要立即返回:

myObject.prototype.myMethodDeferred = function() {
    var deferredObject = $.Deferred();
    var that = this;  // Because setTimeout won't work right with this
    setTimeout(function() { 
        return myMethodActualWork.call(that, deferredObject);
    }, 0);
    return deferredObject.promise();
}

其他回答

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

看看这个: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.

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

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

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

前言:

其他一些答案是正确的,但实际上并没有说明要解决的问题是什么,所以我创建了这个答案来提供详细的说明。

因此,我将详细介绍浏览器的功能以及如何使用setTimeout()。它看起来很长,但实际上非常简单和直接——我只是把它做得非常详细。

更新:我已经做了一个JSFiddle来现场演示下面的解释:http://jsfiddle.net/C2YBE/31/。非常感谢@ThangChung的帮助。

UPDATE2:为了防止JSFiddle网站崩溃或删除代码,我在最后将代码添加到这个答案中。


细节:

想象一个带有“do something”按钮和结果div的web应用程序。

“do something”按钮的onClick处理程序调用了一个函数“LongCalc()”,它做两件事:

计算时间很长(比如3分钟) 将计算结果打印到结果div中。

现在,你的用户开始测试这个,点击“做点什么”按钮,页面停在那里3分钟似乎什么都没做,他们变得焦躁不安,再次点击按钮,等待1分钟,什么都没有发生,再次点击按钮……

问题很明显——您想要一个“Status”DIV,它显示正在发生的事情。让我们看看它是如何工作的。


所以你添加了一个“Status”DIV(最初为空),并修改onclick处理程序(函数LongCalc())做4件事:

填充状态“正在计算…”可能需要大约3分钟”进入状态DIV 计算时间很长(比如3分钟) 将计算结果打印到结果div中。 将状态“计算完成”填充到状态DIV中

而且,你很乐意让用户重新测试应用程序。

他们回来找你的时候看起来很生气。并解释当他们点击按钮时,状态DIV从未更新为“计算…”状态!!


你挠头,在StackOverflow上四处打听(或阅读docs或谷歌),并意识到问题:

浏览器将事件产生的所有“TODO”任务(包括UI任务和JavaScript命令)放入单个队列。不幸的是,用新的“calculation…”值重新绘制“Status”DIV是一个单独的TODO,它会走到队列的末尾!

下面是用户测试期间的事件分解,每个事件之后的队列内容:

Queue: [Empty] Event: Click the button. Queue after event: [Execute OnClick handler(lines 1-4)] Event: Execute first line in OnClick handler (e.g. change Status DIV value). Queue after event: [Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value]. Please note that while the DOM changes happen instantaneously, to re-draw the corresponding DOM element you need a new event, triggered by the DOM change, that went at the end of the queue. PROBLEM!!! PROBLEM!!! Details explained below. Event: Execute second line in handler (calculation). Queue after: [Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value]. Event: Execute 3rd line in handler (populate result DIV). Queue after: [Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result]. Event: Execute 4th line in handler (populate status DIV with "DONE"). Queue: [Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value]. Event: execute implied return from onclick handler sub. We take the "Execute OnClick handler" off the queue and start executing next item on the queue. NOTE: Since we already finished the calculation, 3 minutes already passed for the user. The re-draw event didn't happen yet!!! Event: re-draw Status DIV with "Calculating" value. We do the re-draw and take that off the queue. Event: re-draw Result DIV with result value. We do the re-draw and take that off the queue. Event: re-draw Status DIV with "Done" value. We do the re-draw and take that off the queue. Sharp-eyed viewers might even notice "Status DIV with "Calculating" value flashing for fraction of a microsecond - AFTER THE CALCULATION FINISHED

因此,潜在的问题是,“Status”DIV的重绘制事件被放置在队列的末尾,在“execute line 2”事件之后,该事件需要3分钟,因此实际的重绘制直到计算完成后才发生。


setTimeout()来拯救它。它有什么帮助?因为通过setTimeout调用长时间执行的代码,实际上创建了两个事件:setTimeout执行本身,以及(由于超时为0)正在执行的代码的单独队列条目。

所以,为了解决你的问题,你修改你的onClick处理程序为两个语句(在一个新函数或只是onClick内的一个块):

填充状态“正在计算…”可能需要大约3分钟”进入状态DIV 执行setTimeout(),超时为0,并调用LongCalc()函数。 LongCalc()函数与上次几乎相同,但显然没有“正在计算…”状态DIV更新作为第一步;而是立即开始计算。

那么,事件序列和队列现在是什么样子呢?

Queue: [Empty] Event: Click the button. Queue after event: [Execute OnClick handler(status update, setTimeout() call)] Event: Execute first line in OnClick handler (e.g. change Status DIV value). Queue after event: [Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value]. Event: Execute second line in handler (setTimeout call). Queue after: [re-draw Status DIV with "Calculating" value]. The queue has nothing new in it for 0 more seconds. Event: Alarm from the timeout goes off, 0 seconds later. Queue after: [re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)]. Event: re-draw Status DIV with "Calculating" value. Queue after: [execute LongCalc (lines 1-3)]. Please note that this re-draw event might actually happen BEFORE the alarm goes off, which works just as well. ...

万岁!在计算开始之前,状态DIV刚刚更新为“计算…”!



下面是来自JSFiddle的演示这些示例的示例代码:http://jsfiddle.net/C2YBE/31/:

HTML代码:

<table border=1>
    <tr><td><button id='do'>Do long calc - bad status!</button></td>
        <td><div id='status'>Not Calculating yet.</div></td>
    </tr>
    <tr><td><button id='do_ok'>Do long calc - good status!</button></td>
        <td><div id='status_ok'>Not Calculating yet.</div></td>
    </tr>
</table>

JavaScript代码:(在onDomReady上执行,可能需要jQuery 1.9)

function long_running(status_div) {

    var result = 0;
    // Use 1000/700/300 limits in Chrome, 
    //    300/100/100 in IE8, 
    //    1000/500/200 in FireFox
    // I have no idea why identical runtimes fail on diff browsers.
    for (var i = 0; i < 1000; i++) {
        for (var j = 0; j < 700; j++) {
            for (var k = 0; k < 300; k++) {
                result = result + i + j + k;
            }
        }
    }
    $(status_div).text('calculation done');
}

// Assign events to buttons
$('#do').on('click', function () {
    $('#status').text('calculating....');
    long_running('#status');
});

$('#do_ok').on('click', function () {
    $('#status_ok').text('calculating....');
    // This works on IE8. Works in Chrome
    // Does NOT work in FireFox 25 with timeout =0 or =1
    // DOES work in FF if you change timeout from 0 to 500
    window.setTimeout(function (){ long_running('#status_ok') }, 0);
});