昨天看完對 Timer (定時器) 的基本認識後, 今天來討論一下 setTimeout/setInterval 其他你還不知道的小事吧!
Example:
function delayedMultiply() {
// 當定時器關閉時,6 和 7 這兩個參數會被傳遞給 multiply 函式
setTimeout(multiply, 3000, 5, 6);
}
function multiply(a, b) {
console.log(a * b);
}
// 在傳遞函數的時候誤多寫了()
setTimeout(sayHi(), 3000); //wrong
setTimeout(sayHi, 3000); // correct
setTimeout(delayedMultiply(num1, num2), 3000); //wrong
setTimeout(delayedMultiply, 3000, num1, num2); //correct
那麼好奇寶寶就會問說: 如果你有寫( ), 它實際上會做什麼?
答案是, 該函數會馬上被呼叫,而不是等待 3000 毫秒才被執行!
我們有時需要在函數之間管理計時器, 追踪計時器,以便我們可以使用 clearInterval/clearTimeout 或是知道我們的頁面上計時器是否已經運行。
當我們無法將它們作為 local variable 進行追踪時,最好將它們存為 module-global variable(在 module 模式的範圍內,但程式中的所有函數都可以訪問)。
關於之前提到的 JavaScript 單線程執行以及 event loop, 各個程式任務需要排入 event loop 內的 queue 來等待被執行。不過 timer 的優先權不是最高的,即使 timer 時間已經倒數完了但其他程式還沒執行完的話,也需要等待執行完後, timer 才會被執行。
因此 timer 不是想像中精確的,有時候可能會比 timer 本身設定的 delay 時間還晚才被執行。
setTimeout(function example() {
console.log ('delay 0 sec')
},
console.log ('Hi there!')
Result:
Hi there!
delay 0 sec
雖然 setTimeout 是 delay 0 秒鐘,但是主程序的 call stack 還有 console.log ('Hi there!') 還沒執行完, 因此 queue 中的 example( ) 會等待到 call stack 被清空後才會被執行。可以從這個之前推薦過的網站來觀察整個流程圖:
有 coding 基礎的你回想一下,底下這個函數會立即且按順序打印每一行。如果我們想每 1 秒(1000ms)打印出每一行,那我們應該使用什麼樣的定時器 (timer) 呢?
function startCountDown() {
let count = 10;
for (let i = count; i > 0; i--) {
console.log(i + "...");
}
console.log("0!");
}
startCountDown()
初次嘗試:
function startCountDown() {
let i = 10;
setInterval(function() {
if (i === 0) {
console.log("0!");
} else {
console.log(i + "...");
i--;
}
}, 1000);
}
solution 很接近正解, 不過還有個小問題: 當倒數到 0 時,我們的計時器不會停止
!
再次嘗試: 使用 setInterval
, clearInterval
和設定 timerId
變數
function startCountDown() {
let i = 10;
let timerId = setInterval(function() {
if (i === 0) {
clearInterval(timerId);
console.log("0!");
} else {
console.log(i + "...");
i--;
}
}, 1000);
}
This works!
當呼叫 startCountDown 時,我們為計時器分配一個新的時間間隔 (interval),並在 10 開始倒數計時 1 秒, 1 秒下去… 當我們達到 0 時,我們需要從 window 的任務中把 interval 清除掉。