在計(jì)算機(jī)領(lǐng)域,事件環(huán),或者被稱為消息分發(fā)器,消息環(huán),消息泵或者運(yùn)行環(huán)這些定義不過(guò)是一個(gè)程序結(jié)構(gòu)體,用以在程序中等待,分發(fā)事件或者消息。它的工作方式是向內(nèi)部或者外部的“事件提供方”發(fā)出請(qǐng)求(通常采取封鎖請(qǐng)求的方式,直到有事件發(fā)生),然后再呼叫相應(yīng)的事件處理器(又稱“事件的分發(fā)“)。 事件環(huán)通常于編程設(shè)計(jì)模式” 反應(yīng)器模式“相結(jié)合,前提是事件提供方遵循相同的文件接口, 這樣事件提供方就可以被選擇, '被輪詢' (Unix系統(tǒng)這樣用被動(dòng)方式稱呼,現(xiàn)在也可以直接叫 輪詢). 事件環(huán)幾乎總是對(duì)消息發(fā)出方進(jìn)行異步操作。
當(dāng)一個(gè)事件流被用作程序的中心控制流程, 事實(shí)上它通常做這個(gè)用途, 這時(shí)它又可以被稱為”主環(huán)“或者”主事件環(huán)“。本文標(biāo)題稱為事件環(huán)貼切一點(diǎn),因?yàn)檫@樣的事件環(huán)一直是處在程序的最上的控制層面的。
介紹JavaScript的主要用途主要是用戶互動(dòng),和操作DOM。如果JavaScript同時(shí)有兩個(gè)線程,一個(gè)線程在某個(gè)DOM節(jié)點(diǎn)上添加內(nèi)容,另一個(gè)線程刪除了這個(gè)節(jié)點(diǎn)1,這時(shí)這兩個(gè)節(jié)點(diǎn)會(huì)有很大沖突,為了避免這個(gè)沖突,所以決定了它只能是單線程,否則會(huì)帶來(lái)很復(fù)雜的同步問(wèn)題。此外HTML5提出Web Worker標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個(gè)線程(UI線程, 異步HTTP請(qǐng)求線程, 定時(shí)觸發(fā)器線程...),但是子線程完全受主線程控制,這個(gè)新標(biāo)準(zhǔn)并沒(méi)有改變JavaScript單線程的本質(zhì)。
任務(wù)隊(duì)列
單線程一個(gè)一個(gè)完成任務(wù),前一個(gè)任務(wù)完成了,才會(huì)執(zhí)行下一個(gè)任務(wù),就是排隊(duì)一樣,不能插隊(duì),只能前面的人完成才能輪到后一個(gè)。那么問(wèn)題來(lái)了,加入一個(gè)人在那辦理很多任務(wù),一時(shí)半會(huì)辦不完,難道就一直卡在那里嗎,所有任務(wù)可以分成兩種,一種是同步任務(wù)(synchronous),另一種是異步任務(wù)(asynchronous)。
所有同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧
主線程之外,還存在一個(gè)任務(wù)隊(duì)列。只要異步任務(wù)有了運(yùn)行結(jié)果,就在任務(wù)隊(duì)列之中放置一個(gè)事件。
一旦執(zhí)行棧中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)讀取任務(wù)隊(duì)列,看看里面有哪些事件。那些對(duì)應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài),進(jìn)入執(zhí)行棧,開(kāi)始執(zhí)行。
主線程不斷重復(fù)上面的第三步。
時(shí)間循環(huán)主線程從任務(wù)隊(duì)列中讀取事件,這個(gè)過(guò)程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱為Event Loop(事件循環(huán))
function readi console. log(1) setTimeout(function (( console. log(2) console. log(3);reado分析:setTimeout()的就表示當(dāng)前代碼執(zhí)行完(執(zhí)行棧清空)以后,立即執(zhí)行(0毫秒間隔)指定的回調(diào)函數(shù)。
除了廣義的同步任務(wù)和異步任務(wù),我們對(duì)任務(wù)有更精細(xì)的定義:
Macrotask (宏任務(wù)):
setImmediate:把回調(diào)函數(shù)放在事件隊(duì)列的尾部
setTimeout:定時(shí)器
setInterval:定時(shí)器
Microtask 微任務(wù)):
process.nextTick:把回調(diào)函數(shù)放在當(dāng)前執(zhí)行棧的底部
Promise:
javascript事件環(huán)事件循環(huán)的順序,決定js代碼的執(zhí)行順序。進(jìn)入整體代碼(宏任務(wù))后,開(kāi)始第一次循環(huán)。接著執(zhí)行所有的微任務(wù)。然后再次從宏任務(wù)開(kāi)始,找到其中一個(gè)任務(wù)隊(duì)列執(zhí)行完畢,再執(zhí)行所有的微任務(wù)。
JS是單線程的,也就是說(shuō)任務(wù)需要一個(gè)接一個(gè)的按順序執(zhí)行,這是因?yàn)镴S作為瀏覽器端的腳本語(yǔ)言其開(kāi)始主要用途還是與用戶互動(dòng)和操作DOM,假如JS有兩個(gè)線程,一個(gè)添加DOM一個(gè)刪除DOM,這就勢(shì)必會(huì)出現(xiàn)不可預(yù)期的后果,所以說(shuō)還是單線程更適合,但是這種方式有一個(gè)弊端,就是必須要等待前一個(gè)程序執(zhí)行完畢才執(zhí)行下一個(gè),所以將程序分為了兩類:同步任務(wù)和異步任務(wù)。 在JS的執(zhí)行棧中,同步任務(wù)進(jìn)入主執(zhí)行棧(也可以說(shuō)主線程),而異步任務(wù)進(jìn)入任務(wù)隊(duì)列(TaskQueue)等待執(zhí)行,任務(wù)隊(duì)列可以理解成一個(gè)消息隊(duì)列,I/O設(shè)備完成一件事,就向任務(wù)隊(duì)列添加一個(gè)事件,一旦主執(zhí)行棧中所有的同步任務(wù)執(zhí)行完畢,就會(huì)讀取任務(wù)隊(duì)列中等待的任務(wù),并放入執(zhí)行棧開(kāi)始執(zhí)行,其實(shí)就是執(zhí)行異步任務(wù)的回調(diào)函數(shù),所以說(shuō)異步任務(wù)必須指定回調(diào)函數(shù),主線程會(huì)不斷的循環(huán)這個(gè)動(dòng)作,所以這種運(yùn)行機(jī)制又稱為EventLoop(事件循環(huán))。
本詞條內(nèi)容貢獻(xiàn)者為:
王偉 - 副教授 - 上海交通大學(xué)