Javascript:setTimeout和界面冻结

Jer*_*nce 9 javascript settimeout

上下文

我有大约10个复杂的图表,每个图表需要5秒才能刷新.如果我在这10个图表上进行循环,则刷新大约需要50秒.在这50秒期间,用户可以移动滚动条.如果移动滚动条,则必须停止刷新,当滚动条停止移动时,再次进行刷新.

我在循环中使用setTimeout函数让界面刷新.算法是:

  • 渲染第一个图
  • setTimeout(渲染第二个图形,200)
  • 渲染第二个图形时,在200ms内渲染第三个图形,依此类推

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" />
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

代码确实:

  • 当您将鼠标移动到红色div时,它会更新计数器
  • 当你点击红色div时,它会模拟1秒的大处理(所以它因javascript单线程而冻结界面)
  • 冻结后,等待1ms,然后重新处理处理,依此类推,直到鼠标再次移动
  • 当鼠标再次移动时,它会中断超时以避免无限循环.

问题

当您单击一次并在冻结期间移动鼠标时,我认为将在setTimeout发生时执行的下一个代码是mousemove事件的代码(因此它将取消超时和冻结)但有时由于mouvemove事件,点击计数器获得2点或更多点而不是仅获得1点...

结束此测试:setTimeout函数并不总是释放资源以在mousemove事件期间执行代码,但有时保留线程并在执行另一个代码之前执行settimeout回调内的代码.

这样做的影响是,在我们的实际示例中,用户可以等待10秒(呈现2个图形),而不是在使用滚动条之前等待5秒.这非常烦人,我们需要避免这种情况,并确保在渲染阶段移动滚动条时只渲染一个图形(以及其他取消).

鼠标移动时如何确保中断超时?

PS:在下面的简单示例中,如果您使用200ms更新超时,则所有运行都很完美,但这不是一个可接受的解决方案(真正的问题更复杂,200ms计时器和复杂接口会出现问题).请不要提供"优化图形渲染"的解决方案,这不是问题所在.

编辑:cernunnos对问题有一个更好的解释:另外,通过"阻塞"循环上的进程,你确保在循环完成之前不能处理任何事件,因此任何事件只会被处理(并且超时被清除)每个循环的执行(因此有时你需要在中断之前等待2次或更多次完全执行).

这个问题完全用粗体字表示:我想确保在我想要的时候中断执行,而不是在中断前等待2次或更多次完全执行


第二次编辑:

总结:采取这个jsfiddle:http://jsfiddle.net/BwNca/5/(上面的代码).

更新此jsfiddle并提供以下解决方案:

鼠标在红色div上移动.然后单击并继续移动:右侧计数器必须仅提升一次.但有时它会在第一个计数器再次运行之前提高2到3次......这就是问题,它必须只提升一次!

cer*_*nos 0

我可能已经理解了这个问题,但假设您尝试在单击至少 1 秒后阻止界面,然后通过移动鼠标(最少 1 秒后)来解除阻止:

这不是睡眠的良好实现,因为您一直保持进程运行(什么都不做!=睡眠),这会导致资源浪费。

为什么不创建一个覆盖层(半/全透明的 div),将其放在界面其余部分的顶部(位置固定,全宽和全高)并使用它来防止与底层界面的任何交互。然后在条件合适时销毁它(一秒钟过去并且用户移动鼠标)。

这的行为更像是睡眠(有一些初始处理时间,但随后在给定的时间内释放处理器)并且应该帮助您实现所需的行为(假设我理解正确)。

它还有一个额外的好处,那就是允许您向用户提供一些正在完成某些处理的视觉提示。

编辑:此外,通过“阻止”循环上的进程,您可以确保在该循环完成之前无法处理任何事件,因此任何事件都只会在每个循环的执行之间被处理(并清除超时)(因此为什么您有时必须等待 2 次或更多次完整执行才能中断)。