我有一个UI,我需要动画才能顺利运行。每隔一段时间,我就需要进行一次半大数据计算,使动画跳过,直到计算完成为止。
我试图通过使用setTimeout
进行数据计算 async 来解决这个问题。像setTimeout(calcData(), 0);
整个代码就像这样(简化):
while (animating) {
performAnimation();
if (needCalc) {
setTimeout(calcData(), 0);
}
}
但我仍然在动画中跳过。当我不需要进行任何数据计算时,它运行顺畅。我怎样才能有效地做到这一点?谢谢!
答案 0 :(得分:1)
首先,让我们来谈谈代码中发生了什么:
while (animating) {
performAnimation();
if (needCalc) {
// it should be setTimeout(calcData, 0);
setTimeout(calcData(), 0);
}
}
在行setTimeout(calcData(), 0);
中,您实际上并不推迟调用calcData
函数,而是调用它,因为您在函数名后使用()
运算符。
其次,让我们想一想,当您真正推迟在上面的代码中调用calcData
时会发生什么:通常JavaScript在一个线程中运行,所以,如果您有类似的代码这样:
setTimeout(doSomething, 0);
while (true) {};
永远不会调用 doSomething
,因为javascript的解释器永远执行while
循环,并且它没有“空闲时间”来执行其他事情(甚至是UI)。 setTimeout
- 只是说当解释器空闲时可以安排doSomething
的执行,是时候执行这个功能了。
因此,当浏览器执行javascript函数时,所有其他东西都会冻结。
<强>解决方案强>:
如果您需要处理大数据,可能最好在后端进行计算,然后将结果发送到前端。
通常,当您需要进行一些计算并渲染结果时,最好使用requestAnimationFrame而非while
循环。浏览器将尽快执行requestAnimationFrame
中传递的函数,同时也为浏览器提供处理其他事件的时间。您可以使用requestAnimationFrame
game查看平滑重绘(分步教程here)。
如果你真的想在前端部分处理大量数据,并且想要使ui工作顺利,你可以尝试使用WebWorkers。 WebWorkers看起来像JavaScript中的线程,您需要通过将消息从一个传递到另一个并返回来在主UI“线程”和WebWorker之间进行通信,并且WebWorker上的计算不会影响UI线程。
答案 1 :(得分:1)
您正在看到跳过,因为一次只能运行一个javascript线程。当异步完成某些操作时,javascript引擎会将其放入队列以便稍后运行,然后找到要执行的其他内容。当队列中的某些东西需要完成时,引擎会将其拉出并执行它,阻止所有其他操作直到它完成。然后引擎将其他东西从队列中拉出来执行。
因此,如果您希望允许渲染顺利运行,则必须将计算分解为多个异步调用,从而允许引擎在计算之间安排渲染操作。如果你只是迭代一个数组,这很容易实现,所以你可以做类似的事情:
var now=Date.now;
if(window.performance&&performance.now){//use performace.now if we can
now=performance.now;
}
function calculate(){
var batchSize=10;//If you have a exceptionally long operation you may want to make this lower.
var i=0;
var next=function(){
var start=now();
while(now()-start<14){//14ms / frame
var end=Math.min(i+batchSize,data.length);
for(;i<end;i++){//do batches to reduce time overhead
do_calc(data[i]);
}
}
if(i<data.length) setTimeout(next,1)//defer to next tick
};
next();
}
calculate();
function render(){
do_render_stuff();
if(animating) {
requestAnimationFrame(render);//use requestAnimationFrame rather then setTimeout for rendering
}
}
render();
更好的是,如果可以,您应该使用在不同线程中工作的WebWorkers,与主js引擎完全分离。但是,如果您需要在WebWorker中执行某些操作(例如操作DOM树),则会遇到这种情况。
答案 2 :(得分:0)
大多数情况下,您的问题可归结为您对setTimeout()的错误使用
setTimeout(calcData(), 0);
setTimeout的第一个参数是对要调用的函数的引用。在你的代码中,你没有引用calcData函数,你正在调用它,因为你在函数名之后包含了()。
其次,你为延迟设置0的事实并不意味着你在函数运行之前会有0秒的延迟。 JavaScript在单线程上下文中运行。 setTimeout函数放在一个队列中,并在JavaScript引擎可用时执行,但不会超过最短10ms或您指定的数量(以较小者为准)。
实际上,你的行应该是:
setTimeout(calcData(),10);