setTimeout()
MDN對 setTimeout
的定義為:
在指定的延遲時間之後調用一個函數或執行一個代碼片段。
語法
setTimeout 的語法非常簡單,第一個參數為回調函數,第二個參數為延時的時間。函數返回一個數值類型的ID唯一標示符,此ID可以用作 clearTimeout 的參數來取消定時器:
var timeoutID = window.setTimeout(code, delay);
IE0+ 還支持回調參數的傳入:
var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);
setInterval()
MDN 對 setInterval 的定義為:
周期性地調用一個函數(function)或者執行一段代碼。
由於 setInterval 和 setTimeout 的用法一樣,所以這裡不再列出。
對第二個參數(delay)的說明
由於javascript 的事件循環機制,導致第二個參數並不代表延遲delay毫秒之後立即執行回調函數,而是嘗試將回調函數加入到事件隊列。實際上,setTimeout 和 setInterval 在這一點上處理又存在區別:
所以,當我們的代碼中存在耗時的任務時,定時器並不會表現的如我們所想的那樣。
通過一個例子來理解
下面的代碼,本來希望能夠在 100ms 和 200ms 的時候(也就是剛好等待 100ms)調用回調函數:
var timerStart1 = now(); setTimeout(function () { console.log('第一個setTimeout回調執行等待時間:', now() - timerStart1); var timerStart2 = now(); setTimeout(function () { console.log('第二個setTimeout回調執行等待時間:', now() - timerStart2); }, 100); }, 100); // 輸出: // 第一個setTimeout回調執行等待時間: 106 // 第二個setTimeout回調執行等待時間: 107
這樣的結果看上去正是我們所想的那樣,但是一旦我們在代碼中加入了耗時的任務時候,結果就不像我們所期望的那樣了:
var timerStart1 = now(); setTimeout(function () { console.log('第一個setTimeout回調執行等待時間:', now() - timerStart1); var timerStart2 = now(); setTimeout(function () { console.log('第二個setTimeout回調執行等待時間:', now() - timerStart2); }, 100); heavyTask(); // 耗時任務 }, 100); var loopStart = now(); heavyTask(); // 耗時任務 console.log('heavyTask耗費時間:', now() - loopStart); function heavyTask() { var s = now(); while(now() - s < 1000) { } } function now () { return new Date(); } // 輸出: // heavyTask耗費時間: 1015 // 第一個setTimeout回調執行等待時間: 1018 // 第二個setTimeout回調執行等待時間: 1000
兩個 setTimeout 的等待事件由於耗時任務的存在不再是 100ms 了!我們來描述一下事情的經過:
可以用下面的圖來概括:
再來看 setInterval 的一個例子:
var intervalStart = now(); setInterval(function () { console.log('interval距定義定時器的時間:', now() - loopStart); }, 100); var loopStart = now(); heavyTask(); console.log('heavyTask耗費時間:', now() - loopStart); function heavyTask() { var s = now(); while(now() - s < 1000) { } } function now () { return new Date(); } // 輸出: // heavyTask耗費時間: 1013 // interval距定義定時器的時間: 1016 // interval距定義定時器的時間: 1123 // interval距定義定時器的時間: 1224
上面這段代碼,我們期望每隔 100ms 就打出一條日志。相對於 setTimeout 的區別, setInterval 在准備把回調函數加入到事件隊列的時候,會判斷隊列中是否還有未執行的回調,如果有的話,它就不會再往隊列中添加回調函數。 不然,會出現多個回調同時執行的情況。
可以用下面的圖來概括:
總結
上面對javascript定時器執行原理進行了簡要的分析,希望能夠幫助我們更深入的理解javascript。文中有描述不當的地方可以在評論中指出。