2016-01-05 32 views
2

我正嘗試使用JavaScript來做簡單的計時器。一切都好,但setTimeout()只是即時工作。我該如何解決它?setTimeout()在我的簡單定時器腳本中實時工作

var flag = 0; 

function getTimerValue(){ 
    var val = parseInt(document.getElementById('timer').value,10); 
    if (isNaN(val)) { 
     val = 0; 
    } 
    return val; 
} 

function setTimerValue(newvalue){ 
     document.getElementById('timer').value = newvalue; 
} 

function runTimer(){ 
    if (flag == 0){ 
     flag = 1; 
     while (flag == 1){ 
      x = getTimerValue() 
      if (x < 1){ 
       setTimerValue(0); 
       flag = 0; 
      } else{ 
       setTimeout(setTimerValue(x - 1), 1000); 
      } 
     } 
    } 
} 

回答

1

您的代碼是錯誤的。如果定時器值不爲零,則永久循環,不斷排隊事件以將相同的初始計數器值減1(例如,一次又一次將7更改爲6,但從未更改爲6),但由於調度代碼從未結束,控制從不回傳給JS事件循環以允許它處理任何這些事件。

setTimeout不只是「睡眠指定的時間,然後運行此功能」,它不會阻止;它只是調度稍後發生,並立即返回。預定的呼叫僅在事件循環控制派送事件時處理;由於您的代碼永遠循環,事件循環從未重新獲得控制權。

你需要在JS中異步思考;只要調用異步功能,就不能依賴回調中的任何地方的結果。嘗試重組你的代碼在異步執行的後續工作,所以你實際看到的更新值:

function decrementTimerUntilZero() { 
    var x = getTimerValue(); 
    if (x >= 1) { 
     setTimerValue(x - 1); 
     setTimeout(decrementTimerUntilZero, 1000); // Schedule another decrement 
    } else { 
     setTimerValue(0); 
    } 
} 
if (getTimerValue() > 0) { 
    setTimeout(decrementTimerUntilZero, 1000); 
} 

您最初預定其在遞減,如果是陽性,那麼每次壓縮時間,在計劃新的遞減,如果價值還沒有達到零。

另一種方法(這將有可能獲得比較一致的時間,避免了事件循環調度延遲從未被彌補的問題)是使用setInterval

var timerinterval = setInterval(function() { 
    var x = getTimerValue(); 
    if (x >= 1) { 
     setTimerValue(x - 1); 
    } else { 
     setTimerValue(0); 
     clearInterval(timerinterval); 
    } 
}, 1000); 

在這兩種情況下,你基本上是給當你的函數完成後讓你的控制器可以進行更多的控制,讓事件循環做更多的工作,相信你的回調將在將來可以恢復工作時被正確調用。 JavaScript沒有sleep函數的概念(通過設計;休眠會阻塞事件循環直到完成,凍結瀏覽器UI);這是間歇性地正確完成工作的唯一方法。

如果您想了解更多信息,我建議你看看What do I do if I want a JavaScript version of sleep()?,如果你想獲得怎樣setTimeout工作有更好的瞭解,爲什麼它不只是一個阻塞睡眠等

2

它不工作,因爲你調用setTimerValue功能(這就是爲什麼它被立即執行)。該setTimeout方法需要一個函數的引用,因此其中一個方案是將其改爲:

setTimeout(function() { 
    setTimerValue(x - 1); 
}, 1000); 

或者爲Paulpro points out,你也可以使用:

setTimeout(setTimerValue, 1000, x - 1); 

另外值得一提的是,你可以使用.bind()方法以及:

setTimeout(setTimerValue.bind(null, x - 1), 1000); 
+1

或'的setTimeout(setTimerValue, 1000,x - 1);'除了IE 9之外,它可以在所有相同的瀏覽器中工作,[IE也很容易修復](https://developer.mozilla.org/zh-CN/docs /網絡/ API/WindowTimers/setTimeout的#IE_only_fix))。 – Paulpro

+0

我喜歡你的表演,它只是循環忽略其他語句。 – Noqrax

+0

@Noqrax:你似乎認爲'setTimeout'會阻塞,直到超時結束。它沒有。它稍後在JS事件循環中排隊執行該函數,並立即返回。事件循環中排隊的事件不會在其他JS代碼執行時運行;他們等待它完成。所以你只是排隊無數的事件,應該減少一個相同的計數,循環永遠不會退出,所以事件循環永遠無法發送它們中的任何一個。 – ShadowRanger