[翻譯]JavaScript的定時(shí)器是如何工作的
理解JavaScript定時(shí)器工作原理對(duì)于學(xué)習(xí)JavaScript非常重要。因?yàn)镴avaScript是單線程運(yùn)行的,定時(shí)器使用場(chǎng)合少,不是很直觀。下面通過(guò)三個(gè)函數(shù)來(lái)學(xué)習(xí)JavaScript如何定義,操作及銷毀一個(gè)定時(shí)器。
- var id = setTimeout(fn, delay); - 定義一個(gè)定時(shí)器,在指定時(shí)間delay后調(diào)用函數(shù)fn。函數(shù)返回一個(gè)唯一的標(biāo)識(shí)ID,如果不需要使用這個(gè)定時(shí)器可以用這個(gè)取消。
- var id = setInterval(fn, delay);- 類似setTimeout,但是會(huì)每隔指定時(shí)間delay調(diào)用指定函數(shù)fn,直至取消這個(gè)定時(shí)器。
- clearTimeout(id);clearInterval(id); -這兩個(gè)函數(shù)接受一個(gè)標(biāo)識(shí)ID(分別對(duì)應(yīng)上面兩個(gè)函數(shù)返回的ID),停止定時(shí)器的回調(diào)。
要理解定時(shí)器如何工作,首先要弄清楚一個(gè)概念,定時(shí)器的回調(diào)函數(shù)不能保證在指定時(shí)間delay一定執(zhí)行。由于瀏覽器中的所有JavaScript都在單線程上執(zhí)行,因此異步事件(例如鼠標(biāo)單擊和計(jì)時(shí)器)僅在執(zhí)行中存在同步時(shí)間執(zhí)行完有空缺時(shí)才運(yùn)行。參考下圖:

這張圖里面包含很多信息,要想理解他們需要首先理解JavaScript異步運(yùn)行機(jī)制。這張圖中垂直方向上有時(shí)間刻度,以毫秒為單位,藍(lán)色部分表示正在執(zhí)行的JavaScript事件。例如,第一塊JavaScript執(zhí)行大約18毫秒,Mouse Click Callback大約執(zhí)行11毫秒,后面以此類推。
由于JavaScript是單線程的,不能同時(shí)執(zhí)行兩段JavaScript代碼,所以上面的藍(lán)色的”塊“都是在上一個(gè)塊執(zhí)行完才能執(zhí)行下一個(gè)塊。這意味這當(dāng)一個(gè)異步任務(wù)(例如,點(diǎn)擊鼠標(biāo)事件,定時(shí)器,ajax訪問(wèn))出現(xiàn)的時(shí)候,它將被放入到異步隊(duì)列(放入隊(duì)列的方式和瀏覽器有關(guān),不同瀏覽器有不同的實(shí)現(xiàn))并隨后執(zhí)行。
首先,在第一個(gè)代碼塊中,JavaScript代碼中首先出先兩個(gè)定時(shí)器:10ms Timer starts,10ms Interval starts。這兩個(gè)定時(shí)器的回調(diào)函數(shù)何時(shí)執(zhí)行取決于第一個(gè)代碼塊的所有代碼何時(shí)執(zhí)行完。請(qǐng)注意,由于單線程的原因,它不會(huì)立即執(zhí)行。
同時(shí),在第一個(gè)代碼塊里,還有一個(gè)鼠標(biāo)事件Mouse Click Occurs,和上面的定時(shí)器一樣,這個(gè)異步事件(點(diǎn)擊鼠標(biāo)這種用戶交互是異步執(zhí)行的,因?yàn)镴avaScript不知道用戶什么時(shí)候會(huì)點(diǎn)擊鼠標(biāo))的回調(diào)不會(huì)立即執(zhí)行,而是放在異步隊(duì)列里排隊(duì)等待執(zhí)行。
在初始的代碼塊執(zhí)行完畢之后,瀏覽器隨即開(kāi)始輪詢這個(gè)異步隊(duì)列:有那些操作等待著被執(zhí)行呢?在這個(gè)例子中,鼠標(biāo)點(diǎn)擊。瀏覽器會(huì)選擇一個(gè)立即執(zhí)行(這里是鼠標(biāo)點(diǎn)擊的回調(diào)時(shí)間)。定時(shí)器會(huì)等待指定的時(shí)間delay,然后執(zhí)行。
注意點(diǎn)擊鼠標(biāo)回調(diào)事件在第一個(gè)事件循環(huán),定時(shí)器回調(diào)在隨后的循環(huán)中處理。但是,在后面的事件循環(huán)中(在執(zhí)行定時(shí)器處理程序時(shí)),setTimeout定時(shí)器回調(diào)函數(shù)就會(huì)被拋棄,不再執(zhí)行了。如果在多個(gè)定時(shí)器之后有一大段同步任務(wù)執(zhí)行,則同步任務(wù)執(zhí)行完之后這些定時(shí)器回調(diào)會(huì)被立即執(zhí)行,沒(méi)有延遲(這個(gè)延遲可能很小,就是事件循環(huán)的間隔),直至完成。瀏覽器傾向于等到?jīng)]有更多的異步任務(wù)被加入到異步任務(wù)隊(duì)列中再開(kāi)始執(zhí)行。
實(shí)際上,我們可以看到在事件循環(huán)本身正在執(zhí)行的同時(shí)觸發(fā)了第三個(gè)回調(diào)的情況。 這向我們顯示了一個(gè)重要的事實(shí):事件循環(huán)不關(guān)心當(dāng)前正在執(zhí)行的內(nèi)容,它們會(huì)不加區(qū)分地排隊(duì),即使這意味著從觸發(fā)事件,到滿足條件執(zhí)行回調(diào)函數(shù)之間,有一部分事件被浪費(fèi)掉了。
最后,在第二個(gè)事件回調(diào)完成執(zhí)行之后,我們可以看到JavaScript引擎沒(méi)有執(zhí)行的剩余內(nèi)容。 這意味著瀏覽器現(xiàn)在等待新的異步事件發(fā)生。 當(dāng)間隔再次觸發(fā)時(shí),我們?cè)?0ms處獲得此值。 但是,這次沒(méi)有任何任務(wù)阻止它的執(zhí)行,因此它自己立即觸發(fā)。
讓我們看一個(gè)示例,以更好地說(shuō)明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之后執(zhí)行代碼(只會(huì)多余10毫秒,絕不會(huì)少),setInterval則在每隔10毫秒的延遲時(shí)執(zhí)行回調(diào)代碼,不管上次的回調(diào)是否已經(jīng)執(zhí)行完。
好了,本文中介紹的一些要點(diǎn),現(xiàn)在回顧一下:
- JavaScript引擎在運(yùn)行時(shí)只有一個(gè)線程,從而迫使異步事件排隊(duì)等待執(zhí)行。
- setTimeout和setInterval在執(zhí)行異步代碼的方式上不同。
- 如果定時(shí)器被阻止立即執(zhí)行,它將延遲到下一個(gè)可能的執(zhí)行點(diǎn)(比所需的延遲時(shí)間更長(zhǎng))。
- 如果定時(shí)器延遲的事件足夠長(zhǎng),則在到點(diǎn)后會(huì)立即執(zhí)行,沒(méi)有延遲。
所有這些都是非常重要的基礎(chǔ)知識(shí)。 了解JavaScript引擎的工作原理,尤其是在通常發(fā)生大量異步事件的情況下,為構(gòu)建高級(jí)應(yīng)用程序代碼奠定了良好的基礎(chǔ)。
來(lái)源:https://johnresig.com/blog/how-javascript-timers-work/
本文是John Resig很早的一篇文章,通過(guò)setTimeout,setInterval兩個(gè)函數(shù)的比較,可以了解JavaScript事件循環(huán)機(jī)制。
作者:Tyler Ning
出處:http://www.rzrgm.cn/tylerdonet/
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文連接,如有問(wèn)題,請(qǐng)微信聯(lián)系冬天里的一把火
浙公網(wǎng)安備 33010602011771號(hào)