理解JavaScript定時器工作原理對於學習JavaScript非常重要。因為JavaScript是單線程運行的,定時器使用場合少,不是很直觀。下面通過三個函數來學習JavaScript如何定義,操作及銷毀一個定時器。
- var id = setTimeout(fn, delay); - 定義一個定時器,在指定時間delay后調用函數fn。函數返回一個唯一的標識ID,如果不需要使用這個定時器可以用這個取消。
- var id = setInterval(fn, delay);- 類似setTimeout,但是會每隔指定時間delay調用指定函數fn,直至取消這個定時器。
- clearTimeout(id);clearInterval(id); -這兩個函數接受一個標識ID(分別對應上面兩個函數返回的ID),停止定時器的回調。
要理解定時器如何工作,首先要弄清楚一個概念,定時器的回調函數不能保證在指定時間delay一定執行。由於瀏覽器中的所有JavaScript都在單線程上執行,因此異步事件(例如鼠標單擊和計時器)僅在執行中存在同步時間執行完有空缺時才運行。參考下圖:
這張圖里面包含很多信息,要想理解他們需要首先理解JavaScript異步運行機制。這張圖中垂直方向上有時間刻度,以毫秒為單位,藍色部分表示正在執行的JavaScript事件。例如,第一塊JavaScript執行大約18毫秒,Mouse Click Callback大約執行11毫秒,后面以此類推。
由於JavaScript是單線程的,不能同時執行兩段JavaScript代碼,所以上面的藍色的”塊“都是在上一個塊執行完才能執行下一個塊。這意味這當一個異步任務(例如,點擊鼠標事件,定時器,ajax訪問)出現的時候,它將被放入到異步隊列(放入隊列的方式和瀏覽器有關,不同瀏覽器有不同的實現)並隨后執行。
首先,在第一個代碼塊中,JavaScript代碼中首先出先兩個定時器:10ms Timer starts,10ms Interval starts。這兩個定時器的回調函數何時執行取決於第一個代碼塊的所有代碼何時執行完。請注意,由於單線程的原因,它不會立即執行。
同時,在第一個代碼塊里,還有一個鼠標事件Mouse Click Occurs,和上面的定時器一樣,這個異步事件(點擊鼠標這種用戶交互是異步執行的,因為JavaScript不知道用戶什么時候會點擊鼠標)的回調不會立即執行,而是放在異步隊列里排隊等待執行。
在初始的代碼塊執行完畢之后,瀏覽器隨即開始輪詢這個異步隊列:有那些操作等待着被執行呢?在這個例子中,鼠標點擊。瀏覽器會選擇一個立即執行(這里是鼠標點擊的回調時間)。定時器會等待指定的時間delay,然后執行。
注意點擊鼠標回調事件在第一個事件循環,定時器回調在隨后的循環中處理。但是,在后面的事件循環中(在執行定時器處理程序時),setTimeout定時器回調函數就會被拋棄,不再執行了。如果在多個定時器之后有一大段同步任務執行,則同步任務執行完之后這些定時器回調會被立即執行,沒有延遲(這個延遲可能很小,就是事件循環的間隔),直至完成。瀏覽器傾向於等到沒有更多的異步任務被加入到異步任務隊列中再開始執行。
實際上,我們可以看到在事件循環本身正在執行的同時觸發了第三個回調的情況。 這向我們顯示了一個重要的事實:事件循環不關心當前正在執行的內容,它們會不加區分地排隊,即使這意味着從觸發事件,到滿足條件執行回調函數之間,有一部分事件被浪費掉了。
最后,在第二個事件回調完成執行之后,我們可以看到JavaScript引擎沒有執行的剩余內容。 這意味着瀏覽器現在等待新的異步事件發生。 當間隔再次觸發時,我們在50ms處獲得此值。 但是,這次沒有任何任務阻止它的執行,因此它自己立即觸發。
讓我們看一個示例,以更好地說明setTimeout和setInterval之間的差異。
setTimeout(function(){ /* Some long block of code... */ setTimeout(arguments.callee, 10); }, 10); setInterval(function(){ /* Some long block of code... */ }, 10);
兩者的不同之處在於setTimeout在在10毫秒的delay之后執行代碼(只會多余10毫秒,絕不會少),setInterval則在每隔10毫秒的延遲時執行回調代碼,不管上次的回調是否已經執行完。
好了,本文中介紹的一些要點,現在回顧一下:
- JavaScript引擎在運行時只有一個線程,從而迫使異步事件排隊等待執行。
- setTimeout和setInterval在執行異步代碼的方式上不同。
- 如果定時器被阻止立即執行,它將延遲到下一個可能的執行點(比所需的延遲時間更長)。
- 如果定時器延遲的事件足夠長,則在到點后會立即執行,沒有延遲。
所有這些都是非常重要的基礎知識。 了解JavaScript引擎的工作原理,尤其是在通常發生大量異步事件的情況下,為構建高級應用程序代碼奠定了良好的基礎。
來源:https://johnresig.com/blog/how-javascript-timers-work/
本文是John Resig很早的一篇文章,通過setTimeout,setInterval兩個函數的比較,可以了解JavaScript事件循環機制。