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

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

