[英]JavaScript setInterval() and setTimeout() freezing browser
[英]Javascript : setTimeout and interface freezing
我有大約10個復雜的圖表,每個圖表需要5秒才能刷新。 如果我在這10個圖表上進行循環,則刷新大約需要50秒。 在這50秒期間,用戶可以移動滾動條。 如果移動滾動條,則必須停止刷新,當滾動條停止移動時,再次進行刷新。
我在循環中使用setTimeout函數讓界面刷新。 算法是:
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);
}
代碼確實:
當您單擊一次並在凍結期間移動鼠標時,我認為將在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次......這就是問題,它必須只提升一次!
這里的BIG問題是setTimeout一旦啟動就無法預測,特別是當它正在進行一些繁重的生命時。
你可以在這里看到演示: http : //jsfiddle.net/wao20/C9WBg/
var secTmr = setTimeout(function(){
$('#display').append('Timeout Cleared > ');
clearTimeout(secTmr);
// this will always shown
$('#display').append('I\'m still here! ');
}, 100);
您可以采取兩項措施來最大限度地降低對瀏覽器性能的影響。
存儲setTimeoutID的所有內容,並在您想要停止時循環遍歷它
var timers = []
// When start the worker thread
timers.push( setTimeout(function () { sleep(1000);}, 1) );
// When you try to clear
while (timers.length > 0) {
clearTimeout(timers.pop());
}
當您嘗試停止進程並在工作線程內檢查該標志時設置一個標志,以防clearTimeout無法停止計時器
// Your flag
var STOPForTheLoveOfGod = false;
// When you try to stop
STOPForTheLoveOfGod = true;
while (timers.length > 0) {
clearTimeout(timers.pop());
}
// Inside the for loop in the sleep function
function sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if (STOPForTheLoveOfGod) {
break;
}
// ...
}
}
你可以嘗試這個新腳本。 http://jsfiddle.net/wao20/7PPpS/4/
我可能已經理解了這個問題,但假設你試圖在點擊一下至少1秒后阻止界面並通過移動鼠標解鎖它(在最小1秒之后):
這不是一個很好的睡眠實現,因為你一直在保持進程運行(什么也不做!=休眠),這會浪費資源。
為什么不創建一個疊加(半/完全透明的div),將它放在接口的其余部分(固定位置,全寬和全高)之上,並使用它來防止與底層接口的任何交互。 然后在條件合適時(一秒鍾過去並且用戶移動鼠標)將其銷毀。
這表現得更像睡眠(有一些初始處理時間,但隨后釋放處理器一段時間)並且應該幫助您實現所需的行為(假設我理解正確)。
它還有一個額外的好處,就是允許你給用戶一些正在進行某些處理的視覺提示。
編輯:此外,通過“阻塞”循環上的進程,您確保在該循環完成之前不能處理任何事件,因此任何事件只會在每個循環的執行之間處理(並且超時清除)(因此為什么您有時必須等待2次或更多次完全執行才能中斷)。
令人驚訝的是,當你setTimeout()時,你還沒有想到這一點; 你可以在那之后輸入支票。 變量為true然后廢棄等待,或者將其廢棄。 現在有一種方法可以檢查滾動條滾動。 在使用平均值在變量內檢查它之后,您將發現這將在滾動條形圖時重復無效次數,使許多執行時間為5秒。 要解決此問題,請添加1秒等待,以確保它不會重復。 別客氣 :)
任何長時間運行的功能都會占用您的瀏覽器窗口。 考慮使用WebWorkers在您的主JavaScript代碼之外移動complexAlgorithm()。
答案就在你的問題中
...刷新必須停止,當滾動條停止移動時,刷新再次發生。
您應該以這樣的方式編寫complexAlgorithm,這樣您幾乎可以立即在中間制動它(就在您知道必須重新運行時)
所以主要代碼看起來應該是這樣的
stopAllRefresh; //should instantly(or after completing small chunk) stop refresh
setTimeout(startRefresh, 100);
並在setTimeout中以小塊(每個運行<1秒)渲染圖形
var curentGraph = 0;
var curentChunk = 0;
function renderGraphChunk(){
if (needToBreak) //check if break rendering
{exit};
// Render chunk here
render(curentGraph, curentChunk);
curentChunk +=1;
setTimeout(renderGraphChunk, 1);
}
這只是一個想法草圖,真正的實現可以完全不同
如果沒有網絡工作人員,你想做的事情是不可能完成的,這只是在一些最新的瀏覽器中實現的,特別是Chrome。
否則,您必須在隊列中中斷算法。 就像jQuery UI將每個下一個動畫計算放入隊列中一樣。 http://api.jquery.com/jQuery.queue/
它是一個簡單的隊列,下一個指令集在setTimeout的幫助下排隊。
for (i=0; i <1000; i++)
{
process (i) ;
}
可以翻譯成
function queue(s,n, f)
{
this.i=s;
this.n=n;
this.f=f;
this.step = function(){
if ( this.i <this.n)
{
this.f(this.i);
this.i = this.i +1;
var t = this;
setTimeout( function ( ) { t.step(); } , 5);
}
}
this.step();
}
queue ( O, 1000, function(i){
process(i);
}) ;
這只是一個示例,說明如何使用較小的獨立迭代異步執行同步for循環來執行相同的邏輯。
嘗試檢查網絡工作者。 我認為在這種情況下它會很有用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.