前言:
其他一些答案是正确的,但实际上并没有说明要解决的问题是什么,所以我创建了这个答案来提供详细的说明。
因此,我将详细介绍浏览器的功能以及如何使用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);
});