前言:
其他答案中有一些是正确的,但并未说明解决的问题是什么,因此我创建了这个答案来呈现详细的说明。
因此,我发布了一个详细的演示浏览器工作原理以及如何使用setTimeout()
。看起来很长,但实际上非常简单明了 - 我只是让它变得非常详细。
更新: 我已经制作了一个JSFiddle来实时演示下面的说明: http://jsfiddle.net/C2YBE/31/ 。非常感谢@ThangChung提供帮助。
更新2: 以防万一JSFiddle网站死亡或删除代码,我在本答案的最后添加了代码。
详情:
想象一个带有“做某事”按钮和结果div的Web应用程序。
“做某事”按钮的onClick
处理程序调用一个名为"LongCalc()"的函数,该函数执行以下两项任务:
进行非常长的计算(例如需要3分钟)
将计算结果打印到结果div中。
现在,您的用户开始测试它,单击“做某事”按钮,然后页面似乎无所作为地等待3分钟,他们变得不耐烦,再次单击按钮,等待1分钟,没有任何反应,再次单击按钮……
问题很明显 - 您希望有一个“状态”DIV,显示正在进行的操作。让我们看看它是如何工作的。
因此,您添加了一个“状态”DIV(最初为空),并修改了onclick
处理程序(函数LongCalc()
)以执行4个操作:
将状态“正在计算...可能需要~3分钟”填充到状态DIV中
进行非常长的计算(例如需要3分钟)
将计算结果打印到结果div中。
将状态“计算完成”填充到状态DIV中
然后,您愉快地将应用程序交给用户进行重新测试。
他们生气地回到您这里,并解释说当他们单击该按钮时,状态DIV从未更新为“正在计算...”状态!!!
您摇了摇头,向StackOverflow询问(或阅读文档或搜索),并意识到问题:
浏览器将所有UI任务和JavaScript命令产生的“待办事项”放入单个队列中。不幸的是,使用新的“Calculating…”值重新绘制“Status” DIV是一个单独的待办事项,它会排在队列的末尾!
以下是用户测试期间事件的详细信息以及每个事件后队列的内容:
- 队列:[空]
- 事件:单击按钮。事件后的队列:[执行OnClick处理程序(第1至4行)]
- 事件:执行OnClick处理程序的第一行(例如更改状态DIV值)。事件后的队列:[执行OnClick处理程序(第2至4行),使用新的“Calculating”值重新绘制状态DIV]。请注意,虽然DOM更改是即时发生的,但为了重新绘制相应的DOM元素,您需要一个新的事件,该事件由DOM更改触发,并在队列末尾。
- 问题!问题!下面解释了详情。
- 事件:执行处理程序的第二行(计算)。队列后:[执行OnClick处理程序(第3至4行),使用“Calculating”值重新绘制状态DIV]。
- 事件:执行处理程序的第三行(填充结果DIV)。队列后:[执行OnClick处理程序(第4行),使用结果填充结果DIV;使用结果填充结果DIV,使用结果填充结果DIV]。
- 事件:执行处理程序的第四行(使用“DONE”填充状态DIV)。队列:[执行OnClick处理程序,使用“Calculating”值重新绘制状态DIV,使用结果填充结果DIV;使用“Done”值重新绘制状态DIV]。
- 事件:执行OnClick处理程序子程序中的暗示返回。我们从队列中取出“执行OnClick处理程序”,并开始执行队列中的下一项。
- 注意:由于我们已经完成了计算,因此用户用时已经过去了3分钟。重新绘制事件还没有发生!
- 事件:使用"Calculating"值重新绘制状态DIV。我们进行重新绘制并将其从队列中删除。
- 事件:使用结果值重新绘制结果DIV。我们进行重新绘制并将其从队列中删除。
- 事件:使用“Done”值重新绘制状态DIV。我们进行重新绘制并将其从队列中删除。敏锐的观察者甚至可能会注意到状态DIV与“正在计算”的值在计算结束后闪烁了几微秒。
因此,根本问题在于“Status” DIV的重新绘制事件被放在队列的末尾,在执行需要3分钟的“执行第二行”事件之后,实际的重新绘制不会发生直到计算完成后。使用
setTimeout()
函数可以帮助解决问题。为什么呢?因为通过使用
setTimeout
调用长时间执行的代码,你实际上创建了 2 个事件:
setTimeout
的执行本身,以及(由于0超时),被执行代码的单独队列条目。
所以,为了解决您的问题,在新函数中或在
onClick
中的块中将您的
onClick
处理程序修改为两个语句:
1. 将“正在计算...可能需要~3分钟”状态填充到状态 DIV 中。
2. 使用 0 超时和对
LongCalc()
函数的调用来执行
setTimeout()
。
那么现在事件序列和队列是什么样的呢?
队列:
[空]
事件:单击按钮。 事件后的队列:
[执行 OnClick 处理程序(状态更新, setTimeout() 调用)]
事件:执行 OnClick 处理程序的第一行(例如更改 Status DIV 值)。事件后的队列:
[执行 OnClick 处理程序(它是一个 setTimeout 调用), 重新绘制包含新“计算”的值的 Status DIV]
。
事件:执行处理程序的第二行(setTimeout 调用)。之后的队列:
[重新绘制包含“正在计算”的值的 Status DIV]
。队列在0秒内没有任何新内容。
时间:从超时警报开始,0 秒后。队列后:
[重新绘制包含“正在计算”的值的 Status DIV,执行 LongCalc(第1-3行)]
。
事件:
重新绘制包含“正在计算”的值的 Status DIV。之后的队列:
[执行 LongCalc(第1-3行)]
。请注意,此重绘事件实际上可能发生在警报响起之前,这同样有效。
太好了!计算开始前,状态 DIV 刚刚更新为“正在计算...”!
以下是 JSFiddle 中说明这些示例的示例代码:
http://jsfiddle.net/C2YBE/31/<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;
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');
}
$('#do').on('click', function () {
$('#status').text('calculating....');
long_running('#status');
});
$('#do_ok').on('click', function () {
$('#status_ok').text('calculating....');
window.setTimeout(function (){ long_running('#status_ok') }, 0);
});
setTimeout(fn)
和setTimeout(fn, 0)
是相同的,顺便说一句。 - vsync