前言:
其他一些答案是正确的,但实际上并没有说明要解决的问题是什么,所以我创建了这个答案来展示详细的说明。
因此,我将发布有关浏览器功能以及如何使用setTimeout()
help 的详细演练。它看起来很长,但实际上非常简单明了——我只是把它做得非常详细。
更新:我制作了一个 JSFiddle 来现场演示以下解释:http : //jsfiddle.net/C2YBE/31/。非常感谢@ThangChung 帮助启动它。
UPDATE2:以防万一 JSFiddle 网站死亡,或删除代码,我在最后将代码添加到此答案中。
详情:
想象一个带有“做某事”按钮和结果 div 的网络应用程序。
onClick
“做某事”按钮的处理程序调用一个函数“LongCalc()”,它做两件事:
进行很长的计算(比如需要 3 分钟)
将计算结果打印到结果div中。
现在,你的用户开始测试这个,点击“做点什么”按钮,页面在那里似乎什么都没做 3 分钟,他们变得焦躁不安,再次点击按钮,等待 1 分钟,没有任何react,再次点击按钮......
问题很明显 - 您需要一个“状态”DIV,它显示正在发生的事情。让我们看看它是如何工作的。
因此,您添加一个“状态”DIV(最初为空),并修改onclick
处理程序(函数LongCalc()
)以执行 4 件事:
将状态“正在计算...可能需要约 3 分钟”填充到状态 DIV
进行很长的计算(比如需要 3 分钟)
将计算结果打印到结果div中。
将状态“计算完成”填充到状态 DIV 中
而且,您很高兴将应用程序交给用户重新测试。
他们回到你身边,看起来很生气。并解释说,当他们单击按钮时,状态 DIV 从未更新为“正在计算...”状态!!!
你挠头,在 StackOverflow 上四处询问(或阅读文档或谷歌),然后意识到问题所在:
浏览器将所有由事件产生的“TODO”任务(UI 任务和 JavaScript 命令)放入一个队列中。不幸的是,使用新的“正在计算...”值重新绘制“状态”DIV 是一个单独的 TODO,它会排在队列的末尾!
以下是用户测试期间事件的细分,每个事件后队列的内容:
- 队列:
[Empty]
- 事件:点击按钮。事件后排队:
[Execute OnClick handler(lines 1-4)]
- 事件:在 OnClick 处理程序中执行第一行(例如更改状态 DIV 值)。事件后的队列:
[Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value]
. 请注意,虽然 DOM 更改是即时发生的,但要重新绘制相应的 DOM 元素,您需要一个由 DOM 更改触发的新事件,该事件位于队列的末尾。
- 问题!!! 问题!!!详细说明如下。
- 事件:在处理程序中执行第二行(计算)。排队后:
[Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value]
。
- 事件:在处理程序中执行第三行(填充结果 DIV)。排队后:
[Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result]
。
- 事件:在处理程序中执行第 4 行(用“DONE”填充状态 DIV)。队列:
[Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value]
。
- 事件:执行
return
从onclick
处理程序子隐含。我们从队列中取出“Execute OnClick 处理程序”并开始执行队列中的下一项。
- 注意:由于我们已经完成了计算,对于用户来说已经过去了 3 分钟。重画事件还没有发生!!!
- 事件:使用“正在计算”值重新绘制状态 DIV。我们重新绘制并将其从队列中移除。
- 事件:使用结果值重新绘制结果 DIV。我们重新绘制并将其从队列中移除。
- 事件:使用“完成”值重新绘制状态 DIV。我们重新绘制并将其从队列中移除。眼尖的观众甚至可能会注意到“正在计算”值闪烁的“状态 DIV” -计算完成后
因此,潜在的问题是“状态”DIV 的重绘事件在最后放置在队列中,在“执行第 2 行”事件需要 3 分钟之后,因此实际重绘直到计算完成后。
救援来了setTimeout()
。它有什么帮助?因为通过通过 调用长时间执行的代码setTimeout
,您实际上创建了 2 个事件:setTimeout
执行本身,以及(由于 0 超时)正在执行的代码的单独队列条目。
因此,为了解决您的问题,您将onClick
处理程序修改为两个语句(在一个新函数中或只是一个块中onClick
):
将状态“正在计算...可能需要约 3 分钟”填充到状态 DIV
setTimeout()
以 0 超时和对LongCalc()
function的调用执行。
LongCalc()
功能与上次几乎相同,但显然没有将“正在计算...”状态 DIV 更新作为第一步;而是立即开始计算。
那么,事件序列和队列现在是什么样子的呢?
- 队列:
[Empty]
- 事件:点击按钮。事件后排队:
[Execute OnClick handler(status update, setTimeout() call)]
- 事件:在 OnClick 处理程序中执行第一行(例如更改状态 DIV 值)。事件后的队列:
[Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value]
.
- 事件:在处理程序中执行第二行(setTimeout 调用)。排队后:
[re-draw Status DIV with "Calculating" value]
。队列中还有 0 秒内没有任何新内容。
- 事件:超时警报在 0 秒后关闭。排队后:
[re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)]
。
- 事件:使用“正在计算”值重新绘制状态 DIV。排队后:
[execute LongCalc (lines 1-3)]
。请注意,这个重新绘制事件实际上可能在警报响起之前发生,这也同样有效。
- ...
万岁!在计算开始之前,状态 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上执行并且可能需要 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);
});