說到 javascript 中的定時器,咱們確定會想到 setTimeout()
和 setInterval()
這兩個函數。本文將從 事件循環(Event Loop) 的角度來分析二者的工做原理和區別。javascript
MDN對 setTimeout
的定義爲:java
在指定的延遲時間以後調用一個函數或執行一個代碼片斷。函數
setTimeout
的語法很是簡單,第一個參數爲回調函數,第二個參數爲延時的時間。函數返回一個數值類型的ID惟一標示符,此ID能夠用做 clearTimeout
的參數來取消定時器:oop
var timeoutID = window.setTimeout(code, delay);
IE0+ 還支持回調參數的傳入:spa
var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);
MDN 對 setInterval 的定義爲:3d
週期性地調用一個函數(function)或者執行一段代碼。日誌
因爲 setInterval
和 setTimeout
的用法同樣,因此這裏再也不列出。code
因爲javascript 的事件循環機制,致使第二個參數並不表明延遲delay毫秒以後當即執行回調函數,而是嘗試將回調函數加入到事件隊列。實際上,setTimeout
和 setInterval
在這一點上處理又存在區別:blog
setTimeout:延時delay毫秒以後,啥也無論,直接將回調函數加入事件隊列。隊列
setInterval: 延時delay毫秒以後,先看看事件隊列中是否存在尚未執行的回調函數(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
了!咱們來描述一下事情的通過:
首先,第一個耗時任務(heavyTask()
)開始執行,它須要大約 1000ms
才能執行完畢。
從耗時任務開始執行,過了 100ms
, 第一個 setTimeout
的回調函數指望執行,因而被加入到事件隊列,可是此時前面的耗時任務還沒執行完,因此它只能在隊列中等待,直到耗時任務執行完畢它纔開始執行,因此結果中咱們開的看到的是: 第一個setTimeout回調執行等待時間: 1018
。
第一個 setTimeout
回調一執行,又開啓了第二個 setTimeout
, 這個定時器也是指望延時 100ms
以後可以執行它的回調函數。 可是,在第一個 setTimeout
又存在一個耗時任務,全部它的劇情跟第一個定時器同樣,也等待了 1000ms 纔開始執行。
能夠用下面的圖來歸納:
再來看 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。文中有描述不當的地方能夠在評論中指出。