在談js定時(shí)器以前,我覺得有必要了解下javascript的事件運(yùn)行機(jī)制绅络,簡稱(javascript event loop)阅悍。眾所周知好渠,javascript是單線程,就是說一次只能完成一個(gè)任務(wù)节视,多個(gè)任務(wù)的執(zhí)行需要排隊(duì)拳锚。前一個(gè)任務(wù)結(jié)束,才能執(zhí)行后一個(gè)任務(wù)寻行,如果前一個(gè)任務(wù)耗時(shí)很長霍掺,后面的任務(wù)就處于掛起狀態(tài),不得不等到前一個(gè)任務(wù)完成拌蜘。
js任務(wù)分為同步和異步任務(wù)杆烁。在了解同步和異步之前,需要理解瀏覽器的并發(fā)模型:
左邊的棧存儲的是同步任務(wù)简卧,所謂同步的任務(wù)就是那些能立即執(zhí)行的任務(wù)兔魂,如變量和初始化、事件綁定等等直接聲明調(diào)用的操作举娩。右邊的堆(heap)用來存儲聲明的對象析校。下面的就是任務(wù)隊(duì)列。一個(gè)某個(gè)異步任務(wù)有了響應(yīng)(觸發(fā))铜涉,就會(huì)被推入隊(duì)列中智玻。如用戶的點(diǎn)擊事件、瀏覽器收到服務(wù)的響應(yīng)和之前提到的setTimeout定時(shí)器事件等等芙代。每個(gè)異步任務(wù)都有一個(gè)回調(diào)函數(shù)吊奢。
再來說說setTimeout:
在單線程的Javascript引擎中,setTimeout()是如何運(yùn)行的呢纹烹,這里就要提到瀏覽器內(nèi)核中的事件循環(huán)模型了页滚。簡單的講,在Javascript執(zhí)行引擎之外铺呵,有一個(gè)任務(wù)隊(duì)列逻谦,當(dāng)在代碼中調(diào)用setTimeout()方法時(shí),注冊的延時(shí)方法會(huì)交由瀏覽器內(nèi)核其他模塊(以webkit為例陪蜻,是webcore模塊)處理,當(dāng)延時(shí)方法到達(dá)觸發(fā)條件贱鼻,即到達(dá)設(shè)置的延時(shí)時(shí)間時(shí)宴卖,這一延時(shí)方法被添加至任務(wù)隊(duì)列里。這一過程由瀏覽器內(nèi)核其他模塊處理邻悬,與執(zhí)行引擎主線程獨(dú)立症昏,執(zhí)行引擎在主線程方法執(zhí)行完畢,到達(dá)空閑狀態(tài)時(shí)父丰,會(huì)從任務(wù)隊(duì)列中順序獲取任務(wù)來執(zhí)行肝谭,這一過程是一個(gè)不斷循環(huán)的過程掘宪,稱為事件循環(huán)模型。
當(dāng)一個(gè)異步事件觸發(fā)攘烛,它的回調(diào)函數(shù)先進(jìn)入事件隊(duì)列中排隊(duì)魏滚,任務(wù)隊(duì)列是一個(gè)先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),排在前面的事件坟漱,一旦執(zhí)行棧為空鼠次,優(yōu)先被主線程讀取到棧中執(zhí)行。主線程從任務(wù)隊(duì)列中讀取事件芋齿,這個(gè)過程循環(huán)不斷腥寇,所以整個(gè)的這種運(yùn)行機(jī)制又稱為Event loop。
下面有必要講下 javascript定時(shí)器的工作原理:
有必要看下原文鏈接觅捆,當(dāng)然也能看下袁鍋鍋的Javascript的定時(shí)器的工作原理
為了理解定時(shí)器的內(nèi)部工作原理赦役,我們需要了解一個(gè)非常重要的概念:定時(shí)器設(shè)定的延時(shí)是沒有保證的。因?yàn)樵谒袨g覽器中執(zhí)行的javascript單線程異步事件(比如鼠標(biāo)點(diǎn)擊事件和定時(shí)器)都只有在主線程有空即執(zhí)行棧為空的情況下采取執(zhí)行栅炒。通過圖片來說明:
在解釋上圖時(shí)掂摔,首先先解釋下setTimeout和setInterval的區(qū)別:
setTimeout(fun, delay):延時(shí)delay毫秒之后,啥也不管职辅,直接將回調(diào)函數(shù)推入事件隊(duì)列
setInterval(fun, delay):延時(shí)delay毫秒之后棒呛,先看看事件隊(duì)列中是否存在還沒有執(zhí)行的回調(diào)函數(shù)(setInterval的回調(diào)函數(shù)),如果存在域携,就不需要再往事件隊(duì)列中加入回調(diào)函數(shù)了簇秒。
我們再來解釋上面的圖。
上圖藍(lán)色區(qū)域表示任務(wù)的執(zhí)行時(shí)間秀鞭,首先是Javascript代表的同步任務(wù)趋观。在10ms處注冊了一個(gè)setTimeout事件,并且在之后又多了一個(gè)鼠標(biāo)點(diǎn)擊事件锋边,在鼠標(biāo)點(diǎn)擊事件后又多了個(gè)setInterval事件皱坛。
當(dāng)setTimeout相對注冊該事件過了10ms時(shí),開始觸發(fā)事件豆巨∈1伲可是現(xiàn)在藍(lán)色區(qū)域的同步任務(wù)還未執(zhí)行完,即主線程任務(wù)未執(zhí)行完往扔,執(zhí)行棧還不為空贩猎。則把該處的回調(diào)函數(shù)推入事件隊(duì)列。然而萍膛,事件隊(duì)列中已經(jīng)有一個(gè)點(diǎn)擊觸發(fā)的事件吭服,因?yàn)樗榷〞r(shí)器過10秒才觸發(fā)快,所以優(yōu)先進(jìn)入到隊(duì)列蝗罗。
等Javascript代表的藍(lán)色區(qū)域同步任務(wù)執(zhí)行完之后艇棕,主線程便從任務(wù)隊(duì)列中取到讀到鼠標(biāo)點(diǎn)擊事件蝌戒,開始執(zhí)行mouse click callback的藍(lán)色區(qū)域的回調(diào)函數(shù),然而在這個(gè)區(qū)域中setInterval的離注冊事件過10ms到時(shí)事件開始觸發(fā)沼琉,并將回調(diào)函數(shù)推入事件隊(duì)列中北苟。此時(shí)它的前面排著10ms setTimeout事件。等Mouse Click回調(diào)函數(shù)執(zhí)行完刺桃,執(zhí)行棧又為空粹淋,推入10ms setTimeout事件,并執(zhí)行瑟慈,此時(shí)在Timer藍(lán)色區(qū)域下一個(gè)10ms setInterval事件觸發(fā)桃移,由于之前事件隊(duì)列中有一個(gè)interval事件了,則丟棄葛碧,不進(jìn)入隊(duì)列借杰。
等Timer的回調(diào)執(zhí)行完,隨即執(zhí)行事件隊(duì)列里的第一個(gè)Interval事件进泼,在Interval藍(lán)色執(zhí)行期間蔗衡,又有個(gè)interval事件觸發(fā),則推入事件隊(duì)列乳绕,即本次Interval事件執(zhí)行完下一個(gè)直接取到執(zhí)行棧執(zhí)行绞惦,下面的setInterval事件可謂是暢通無阻了,按照每10ms執(zhí)行~
表達(dá)的不清楚洋措,請見諒济蝉,本文也是結(jié)合了幾篇講解定時(shí)器文章做了一個(gè)總結(jié)。
參考鏈接:
javascript計(jì)時(shí)器工作原理
javascript定時(shí)器工作原理
javascript單線程異步機(jī)制
阮一峰:再談event loop