JavaScript的setTimeout與setInterval是兩個(gè)很容易欺騙別人感情的方法,因?yàn)槲覀冮_始常常以為調(diào)用了就會(huì)按既定的方式執(zhí)行, 我想不少人都深有同感
例如
由圖可看出,瀏覽器中的JavaScript引擎是基于事件驅(qū)動(dòng)的,這里的事件可看作是瀏覽器派給它的各種任務(wù),這些任務(wù)可以源自JavaScript引擎當(dāng)前執(zhí)行的代碼塊,如調(diào)用setTimeout添加一個(gè)任務(wù),也可來自瀏覽器內(nèi)核的其它線程,如界面元素鼠標(biāo)點(diǎn)擊事件,定時(shí)觸發(fā)器時(shí)間到達(dá)通知,異步請求狀態(tài)變更通知等.從代碼角度看來任務(wù)實(shí)體就是各種回調(diào)函數(shù),JavaScript引擎一直等待著任務(wù)隊(duì)列中任務(wù)的到來.由于單線程關(guān)系,這些任務(wù)得進(jìn)行排隊(duì),一個(gè)接著一個(gè)被引擎處理.
上圖t1-t2..tn表示不同的時(shí)間點(diǎn),tn下面對應(yīng)的小方塊代表該時(shí)間點(diǎn)的任務(wù),假設(shè)現(xiàn)在是t1時(shí)刻,引擎運(yùn)行在t1對應(yīng)的任務(wù)方塊代碼內(nèi),在這個(gè)時(shí)間點(diǎn)內(nèi),我們來描述一下瀏覽器內(nèi)核其它線程的狀態(tài).
t1時(shí)刻:
GUI渲染線程:
該線程負(fù)責(zé)渲染瀏覽器界面HTML元素,當(dāng)界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時(shí),該線程就會(huì)執(zhí)行.本文雖然重點(diǎn)解釋JavaScript定時(shí)機(jī)制,但這時(shí)有必要說說渲染線程,因?yàn)樵摼€程與JavaScript引擎線程是互斥的,這容易理解,因?yàn)镴avaScript腳本是可操縱DOM元素,在修改這些元素屬性同時(shí)渲染界面,那么渲染線程前后獲得的元素?cái)?shù)據(jù)就可能不一致了.
在JavaScript引擎運(yùn)行腳本期間,瀏覽器渲染線程都是處于掛起狀態(tài)的,也就是說被”凍結(jié)”了.
所以,在腳本中執(zhí)行對界面進(jìn)行更新操作,如添加結(jié)點(diǎn),刪除結(jié)點(diǎn)或改變結(jié)點(diǎn)的外觀等更新并不會(huì)立即體現(xiàn)出來,這些操作將保存在一個(gè)隊(duì)列中,待JavaScript引擎空閑時(shí)才有機(jī)會(huì)渲染出來.
GUI事件觸發(fā)線程:
JavaScript腳本的執(zhí)行不影響html元素事件的觸發(fā),在t1時(shí)間段內(nèi),首先是用戶點(diǎn)擊了一個(gè)鼠標(biāo)鍵,點(diǎn)擊被瀏覽器事件觸發(fā)線程捕捉后形成一個(gè)鼠標(biāo)點(diǎn)擊事件,由圖可知,對于JavaScript引擎線程來說,這事件是由其它線程異步傳到任務(wù)隊(duì)列尾的,由于引擎正在處理t1時(shí)的任務(wù),這個(gè)鼠標(biāo)點(diǎn)擊事件正在等待處理.
定時(shí)觸發(fā)線程:
注意這里的瀏覽器模型定時(shí)計(jì)數(shù)器并不是由JavaScript引擎計(jì)數(shù)的,因?yàn)镴avaScript引擎是單線程的,如果處于阻塞線程狀態(tài)就計(jì)不了時(shí),它必須依賴外部來計(jì)時(shí)并觸發(fā)定時(shí),所以隊(duì)列中的定時(shí)事件也是異步事件.
由圖可知,在這t1的時(shí)間段內(nèi),繼鼠標(biāo)點(diǎn)擊事件觸發(fā)后,先前已設(shè)置的setTimeout定時(shí)也到達(dá)了,此刻對JavaScript引擎來說,定時(shí)觸發(fā)線程產(chǎn)生了一個(gè)異步定時(shí)事件并放到任務(wù)隊(duì)列中, 該事件被排到點(diǎn)擊事件回調(diào)之后,等待處理.
同理, 還是在t1時(shí)間段內(nèi),接下來某個(gè)setInterval定時(shí)器也被添加了,由于是間隔定時(shí),在t1段內(nèi)連續(xù)被觸發(fā)了兩次,這兩個(gè)事件被排到隊(duì)尾等待處理.
可見,假如時(shí)間段t1非常長,遠(yuǎn)大于setInterval的定時(shí)間隔,那么定時(shí)觸發(fā)線程就會(huì)源源不斷的產(chǎn)生異步定時(shí)事件并放到任務(wù)隊(duì)列尾而不管它們是否已被處理,但一旦t1和最先的定時(shí)事件前面的任務(wù)已處理完,這些排列中的定時(shí)事件就依次不間斷的被執(zhí)行,這是因?yàn)?對于JavaScript引擎來說,在處理隊(duì)列中的各任務(wù)處理方式都是一樣的,只是處理的次序不同而已.
t1過后,也就是說當(dāng)前處理的任務(wù)已返回,JavaScript引擎會(huì)檢查任務(wù)隊(duì)列,發(fā)現(xiàn)當(dāng)前隊(duì)列非空,就取出t2下面對應(yīng)的任務(wù)執(zhí)行,其它時(shí)間依此類推,由此看來:
如果隊(duì)列非空,引擎就從隊(duì)列頭取出一個(gè)任務(wù),直到該任務(wù)處理完,即返回后引擎接著運(yùn)行下一個(gè)任務(wù),在任務(wù)沒返回前隊(duì)列中的其它任務(wù)是沒法被執(zhí)行的.
相信您現(xiàn)在已經(jīng)很清楚JavaScript是否可多線程,也了解理解JavaScript定時(shí)器運(yùn)行機(jī)制了,下面我們來對一些案例進(jìn)行分析:
案例1:setTimeout與setInterval
更多建議: