JavaScript事件循環(huán)機(jī)制的深入理解
首先,我們要理解JavaScript是一門(mén)單線(xiàn)程的語(yǔ)言。所謂單線(xiàn)程,簡(jiǎn)單來(lái)說(shuō)一個(gè)時(shí)間只能做一件事,只有做完這件事,才能進(jìn)行下一件。那為什么選擇單線(xiàn)程,不選擇多線(xiàn)程呢?這是由JS的用途決定的,JS的用途是與用戶(hù)交互,以及操作DOM,假設(shè)JS有兩個(gè)線(xiàn)程,一個(gè)要在某個(gè)DOM節(jié)點(diǎn)上添加內(nèi)容,一個(gè)要?jiǎng)h除這個(gè)節(jié)點(diǎn),那瀏覽器該以哪個(gè)為準(zhǔn)呢,事情就變得復(fù)雜了。因此,JS在誕生時(shí)就是單線(xiàn)程,以后也不會(huì)改變。
這個(gè)時(shí)候就出現(xiàn)了問(wèn)題,如果一件任務(wù)耗時(shí)太長(zhǎng),就會(huì)阻塞后面的任務(wù)。這樣肯定是不行的,所以,JS設(shè)計(jì)者將任務(wù)分成同步任務(wù)和異步任務(wù)。
同步任務(wù)就是在主線(xiàn)程(調(diào)用棧 Call Stack)中按照書(shū)寫(xiě)順序依次執(zhí)行的任務(wù)。異步任務(wù)就是不阻塞主線(xiàn)程,而是交由其他線(xiàn)程或系統(tǒng)處理(如瀏覽器 或 Node.js ),處理完畢后,將其回調(diào)函數(shù)推入任務(wù)隊(duì)列。等到主線(xiàn)程空了,任務(wù)隊(duì)列中的任務(wù)再根據(jù)FIFO算法被調(diào)度到主線(xiàn)程中執(zhí)行。
事件循環(huán)的工作流程如下:
1.執(zhí)行調(diào)用棧中的所有同步代碼,直到????!緱J呛筮M(jìn)先出結(jié)構(gòu),但是同步代碼入棧后直接執(zhí)行,執(zhí)行完畢彈出,所以順序還是不變的,但如果函數(shù)嵌套,則調(diào)用??赡艽嬖诙鄬雍瘮?shù)上下文,最后進(jìn)入的上下文最先執(zhí)行完,所以最先彈出】
2.檢查任務(wù)隊(duì)列,依次將任務(wù)調(diào)度到調(diào)用棧中執(zhí)行,直到隊(duì)列空。
3.重復(fù)循環(huán)。
只要調(diào)用??樟?,就去任務(wù)隊(duì)列中讀取任務(wù),【用戶(hù)點(diǎn)擊按鈕觸發(fā)函數(shù),函數(shù)就會(huì)被推入調(diào)用棧執(zhí)行】這個(gè)過(guò)程不斷重復(fù)循環(huán),這就是JavaScript的運(yùn)行機(jī)制, 這種機(jī)制就叫做事件循環(huán)機(jī)制
所以有一個(gè)小細(xì)節(jié)就是setTimeout(callback,s)的真正含義并不是在指定的毫秒數(shù)后調(diào)用函數(shù),而是最快s毫秒后調(diào)用函數(shù),因?yàn)樗枰却骶€(xiàn)程空后再被調(diào)用。
但是這里面還有更細(xì)節(jié)的問(wèn)題,在事件循環(huán)的早期設(shè)計(jì)中,所有異步任務(wù)都進(jìn)入同一個(gè)任務(wù)隊(duì)列。但隨著前端復(fù)雜度提升,任務(wù)優(yōu)先級(jí)問(wèn)題顯露出來(lái):
緊急任務(wù)需要優(yōu)先處理(如 Promise 狀態(tài)更新)
非緊急任務(wù)可以延后(如 UI 渲染前的計(jì)算)
因此,現(xiàn)代事件循環(huán)又將異步任務(wù)細(xì)分為兩類(lèi):

同步任務(wù)與宏任務(wù):整個(gè)腳本(主線(xiàn)程代碼),即script標(biāo)簽里的代碼,本身就是一個(gè)宏任務(wù),主線(xiàn)程執(zhí)行腳本中的同步代碼屬于初始宏任務(wù)。整個(gè)腳本的執(zhí)行是第一個(gè)宏任務(wù),同步代碼是它的組成部分。
于是,事件循環(huán)的工作流程就變成:
1.按照代碼書(shū)寫(xiě)順序執(zhí)行初始宏任務(wù):
如果遇到同步代碼,直接推入調(diào)用棧執(zhí)行。
如果遇到宏任務(wù),將回調(diào)函數(shù)推入宏任務(wù)隊(duì)列。
如果遇到微任務(wù),將回調(diào)函數(shù)推入微任務(wù)隊(duì)列。
2.清空微任務(wù)隊(duì)列:
當(dāng)前宏任務(wù)執(zhí)行完畢后,依次將微任務(wù)隊(duì)列中的所有微任務(wù)推入調(diào)用棧執(zhí)行,直到微任務(wù)隊(duì)列清空。
注意:若微任務(wù)中又生成新的微任務(wù),新微任務(wù)也會(huì)在此階段被立即執(zhí)行。
3.渲染更新(如有必要):
瀏覽器判斷是否需要渲染(通常根據(jù)屏幕刷新率,如 60Hz 對(duì)應(yīng)約 16.6ms/次)。
4.開(kāi)啟下一輪事件循環(huán):
從宏任務(wù)隊(duì)列中取出下一個(gè)宏任務(wù)執(zhí)行,重復(fù)上述流程。
Tips:我的一些思路歷程
剛開(kāi)始我以為js在遇到settimeout這種定時(shí)器時(shí),會(huì)將整個(gè)定時(shí)器函數(shù)推到宏任務(wù)隊(duì)列中,但其實(shí)不是的,
【如果是這樣的話(huà),那一個(gè)3秒的宏任務(wù),會(huì)和一個(gè)耗費(fèi)8秒的微任務(wù)幾乎同時(shí)分別推入宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列,那這個(gè)時(shí)候如果調(diào)用棧執(zhí)行空了,先取出這個(gè)微任務(wù)執(zhí)行,那可能8s的打印出來(lái)了,定時(shí)器還沒(méi)打印出來(lái),這個(gè)深究的話(huà)這個(gè)理解是錯(cuò)誤的,不過(guò)可以幫我記住任務(wù)隊(duì)列里放的都是處理過(guò)的回調(diào)】
js遇到定時(shí)器之后,將定時(shí)器以及回調(diào)交給瀏覽器,由瀏覽器的定時(shí)模塊發(fā)起定時(shí),處理完畢后(例如三秒后)將回調(diào)函數(shù)推入宏任務(wù)隊(duì)列。
接著列舉一些宏任務(wù)和微任務(wù)的理解,以及他們是怎么分類(lèi)的
宏任務(wù):
由宿主環(huán)境發(fā)起,比如瀏覽器或者Node。js遇到這些任務(wù)時(shí)會(huì)向?qū)?yīng)的環(huán)境發(fā)起請(qǐng)求,由瀏覽器或者node進(jìn)行處理,處理完畢后推入宏任務(wù)隊(duì)列
瀏覽器環(huán)境: setTimeout、setInterval【定時(shí)器觸發(fā)線(xiàn)程】、DOM事件【事件觸發(fā)線(xiàn)程、GUI渲染線(xiàn)程】、AJAX回調(diào)【瀏覽器網(wǎng)絡(luò)線(xiàn)程】等需要瀏覽器線(xiàn)程協(xié)作的任務(wù)
Node環(huán)境: I/O操作、setImmediate等系統(tǒng)級(jí)異步操作
微任務(wù):
由JavaScript引擎自身發(fā)起和管理【相當(dāng)于是一種js本身的異步機(jī)制】,例如js遇到promise的時(shí)候,會(huì)執(zhí)行promise的同步部分,然后將promise的狀態(tài)標(biāo)記為pending,而直到當(dāng)resolve/reject被調(diào)用時(shí),js才會(huì)將對(duì)應(yīng)的.then或者.catch回調(diào)函數(shù)推入微任務(wù)隊(duì)列
語(yǔ)言級(jí)實(shí)現(xiàn):Promise.then/catch/finally、queueMicrotask等屬于ECMAScript規(guī)范定義的異步機(jī)制
到此這篇關(guān)于JavaScript事件循環(huán)機(jī)制的文章就介紹到這了,更多相關(guān)js事件循環(huán)機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
bootstrap動(dòng)態(tài)添加面包屑(breadcrumb)及其響應(yīng)事件的方法
這篇文章主要介紹了bootstrap動(dòng)態(tài)添加面包屑(breadcrumb)及其響應(yīng)事件的方法,涉及js數(shù)據(jù)傳輸及定義響應(yīng)事件相關(guān)操作技巧,需要的朋友可以參考下2017-05-05
JS動(dòng)態(tài)給對(duì)象添加屬性和值的實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇JS動(dòng)態(tài)給對(duì)象添加屬性和值的實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10
關(guān)于網(wǎng)頁(yè)中的無(wú)縫滾動(dòng)的js代碼
隨便打開(kāi)一個(gè)網(wǎng)頁(yè),基本上都會(huì)看到無(wú)縫滾動(dòng)或者輪播圖,比如淘寶還有360官網(wǎng)的首頁(yè)。那么這些滾動(dòng)效果是怎么實(shí)現(xiàn)的呢?下面小編給大家分享關(guān)于網(wǎng)頁(yè)中的無(wú)縫滾動(dòng)的js代碼,感興趣的朋友一起看下吧2016-06-06
前端實(shí)現(xiàn)一鍵截圖從原理到避坑的完整實(shí)戰(zhàn)指南
在前端開(kāi)發(fā)過(guò)程中,截圖可是個(gè)相當(dāng)重要的環(huán)節(jié),它能幫助我們直觀(guān)地展示頁(yè)面的設(shè)計(jì)效果、交互狀態(tài),還能用于記錄問(wèn)題、進(jìn)行對(duì)比分析等,這篇文章主要介紹了前端實(shí)現(xiàn)一鍵截圖從原理到避坑的相關(guān)資料,需要的朋友可以參考下2025-09-09
css和js實(shí)現(xiàn)彈出登錄居中界面完整代碼
這篇文章主要介紹了css和js實(shí)現(xiàn)彈出登錄居中界面,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-11-11

