语境
我有大约 10 个复杂的图表,每个需要 5 秒来刷新。如果我对这 10 个图表进行循环,刷新大约需要 50 秒。在这 50 秒内,用户可以移动滚动条。如果滚动条被移动,刷新必须停止,当滚动条停止移动时,刷新再次发生。
我在循环中使用 setTimeout 函数让界面刷新。算法是:
- 渲染第一张图
- setTimeout(渲染第二张图,200)
- 渲染第二个图时,在 200 毫秒内渲染第三个图,依此类推
setTimeout 允许我们捕获滚动条事件并在下一次刷新时清除超时以避免在移动滚动条之前等待 50 秒...
问题是它不会随时运行。
使用下面的简单代码(您可以在这个小提琴中尝试:http: //jsfiddle.net/BwNca/5/):
HTML:
<div id="test" style="width: 300px;height:300px; background-color: red;">
</div>
<input type="text" id="value" />
<input type="text" id="value2" />
Javascript:
var i = 0;
var j = 0;
var timeout;
var clicked = false;
// simulate the scrollbar update : each time mouse move is equivalent to a scrollbar move
document.getElementById("test").onmousemove = function() {
// ignore first move (because onclick send a mousemove event)
if (clicked) {
clicked = false;
return;
}
document.getElementById("value").value = i++;
clearTimeout(timeout);
}
// a click simulates the drawing of the graphs
document.getElementById("test").onclick = function() {
// ignore multiple click
if (clicked) return;
complexAlgorithm(1000);
clicked = true;
}
// simulate a complexe algorithm which takes some time to execute (the graph drawing)
function complexAlgorithm(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
document.getElementById("value2").value = j++;
// launch the next graph drawing
timeout = setTimeout(function() {complexAlgorithm(1000);}, 1);
}
代码确实:
- 当您将鼠标移入红色 div 时,它会更新一个计数器
- 当您单击红色 div 时,它会模拟 1 秒的大处理(因此由于 javascript 单线程,它会冻结界面)
- 冻结后等待1ms,重新模拟处理等直到鼠标再次移动
- 当鼠标再次移动时,它会打破超时以避免无限循环。
问题
当您单击一次并在冻结期间移动鼠标时,我在想当 setTimeout 发生时将执行的下一个代码是 mousemove 事件的代码(因此它会取消超时和冻结)但有时由于 mouvemove 事件,点击计数器获得 2 分或更多分,而不是仅获得 1 分...
该测试的结论:setTimeout 函数并不总是在 mousemove 事件期间释放资源来执行代码,但有时会在执行另一个代码之前保留线程并在 settimeout 回调中执行代码。
这样做的影响是,在我们的实际示例中,用户可以等待 10 秒(渲染 2 个图表)而不是在使用滚动条之前等待 5 秒。这很烦人,我们需要避免这种情况,并确保在渲染阶段移动滚动条时只渲染一个图形(而其他图形被取消)。
鼠标移动时如何确定超时?
PS:在下面的简单示例中,如果将超时更新为 200 毫秒,则一切运行正常,但这不是一个可接受的解决方案(真正的问题更复杂,问题发生在 200 毫秒的计时器和复杂的接口上)。请不要提供“优化图形渲染”的解决方案,这不是这里的问题。
编辑: cernunnos 对问题有更好的解释:此外,通过“阻止”循环中的进程,您可以确保在该循环完成之前无法处理任何事件,因此只会处理任何事件(并清除超时)每个循环的执行(因此有时您必须等待 2 次或更多次完整执行才能中断)。
问题恰好包含在粗体字中:我想确保在我想要的时候中断执行,而不是在中断之前等待 2 次或更多次完整执行
第二次编辑:
总之:采用这个jsfiddle: http: //jsfiddle.net/BwNca/5/(上面的代码)。
更新此 jsfiddle 并提供解决方案:
鼠标在红色 div 上移动。然后单击并继续移动:右计数器必须只加一次。但有时它会在第一个计数器再次运行之前加注 2 或 3 次……这就是问题所在,它必须只加注一次!