事件循環(huán)機制:JavaScript被設(shè)計為單線程,那如何做到異步的呢?
JavaScript是單線程的語言,單線程是指所有的程序路徑按照一定的順序執(zhí)行,只有前面的程序執(zhí)行了,后面的程序才會執(zhí)行。
也就是說在同一時間,JavaScript只能做一件事情,為了協(xié)調(diào)瀏覽器產(chǎn)生的各種事件、網(wǎng)絡(luò)處理、前端渲染等行為,js的事件循環(huán)機制,即EventLoop應(yīng)運而生。
JavaScript是單線程的原因
js的設(shè)計初衷是作為瀏覽器的腳本語言,瀏覽器中涉及到與用戶互動、頻繁操作DOM等動作,如果js設(shè)計為多線程,會有很復(fù)雜的線程同步問題,即使同步問題被解決,也會降低瀏覽器的響應(yīng)效率,得不償失,因此,JavaScript被設(shè)計為單線程保證瀏覽器動作的一致性。
事件循環(huán)(EventLoop)
JavaScript既然被設(shè)計為單線程,是如何做到異步的呢?這時就用到了JavaScript的事件循環(huán)機制。
如下圖所示為JavaScript事件循環(huán)的原理圖。
如圖所示,事件循環(huán)是主線程循環(huán)讀取任務(wù)隊列中的任務(wù),直到所有的任務(wù)都被執(zhí)行。在事件循環(huán)中,JavaScript用到了棧、堆以及隊列等數(shù)據(jù)結(jié)構(gòu)。
- 棧中存放的是執(zhí)行上下文,有函數(shù)被調(diào)用時,就會創(chuàng)建上下文存放在執(zhí)行棧中。
- 堆中表示一個非結(jié)構(gòu)化的內(nèi)存區(qū)域,用來存放對象。隊列是指任務(wù)隊列,用于存放異步任務(wù)。
- js引擎遇到一個異步事件之后不會一直等待事件的返回結(jié)果,而是將事件掛起,繼續(xù)執(zhí)行執(zhí)行棧中的其他任務(wù)。
當異步事件返回結(jié)果時,js將異步事件callback函數(shù)放入隊列中,被放入隊列中的異步事件不會立即回調(diào),等到當前執(zhí)行棧中的任務(wù)都執(zhí)行完成,處于閑置狀態(tài)的主線程按照隊列順序?qū)⑻幱谑孜皇录腸allback函數(shù)放入執(zhí)行棧中,執(zhí)行該函數(shù)的同步代碼,如果遇到了異步事件,同樣也會將其回調(diào)函數(shù)放入事件隊列中......
如此反復(fù),就形成了一個無限循環(huán),這也是被稱為“事件循環(huán)(EventLoop)”的原因。
宏任務(wù)(Micro task)和微任務(wù)(Macro task)
js事件循環(huán)的基本原理已經(jīng)描述清楚,但是異步任務(wù)之間也有所不同。
上面講到,JavaScript在執(zhí)行異步任務(wù)時,回調(diào)函數(shù)會被放在js的任務(wù)隊列中,實際上,回調(diào)函數(shù)的類別不同,執(zhí)行的優(yōu)先級也不同。
不同的優(yōu)先級被分為兩類,一類是宏任務(wù)(Micro task),一類是微任務(wù)(Macro task)。
回調(diào)函數(shù)是微任務(wù)時,會被放在微任務(wù)隊列,回調(diào)函數(shù)是宏任務(wù)時,會被放在宏任務(wù)隊列。微任務(wù)的優(yōu)先級高于宏任務(wù),當主線程的任務(wù)執(zhí)行完成時,會首先去執(zhí)行微任務(wù)隊列中首位的回調(diào)函數(shù),當微任務(wù)隊列中為空時,才回去執(zhí)行宏任務(wù)隊列中的回調(diào)函數(shù)。
常見的微任務(wù)有:promise,常見的宏任務(wù)有setInterval等。
因此,事件循環(huán)的執(zhí)行流程圖如下所示:
? ?