JS的運(yùn)行機(jī)制之事件循環(huán)機(jī)制示例詳解
前言
在現(xiàn)代Web開發(fā)中,JavaScript是構(gòu)建動態(tài)交互體驗(yàn)的核心。但它的運(yùn)行機(jī)制卻常被誤解——一個(gè)看似“單線程”的語言如何高效處理復(fù)雜的異步操作?JavaScript 是一門以事件驅(qū)動、單線程執(zhí)行為核心特征的編程語言。盡管它在執(zhí)行代碼時(shí)只有一個(gè)主線程,但卻能高效地處理各種異步任務(wù),如定時(shí)器、網(wǎng)絡(luò)請求、用戶交互等。這背后,正是強(qiáng)大的事件循環(huán)機(jī)制在默默運(yùn)轉(zhuǎn)。本文將從瀏覽器的進(jìn)程模型開始,逐步揭開 JavaScript 事件循環(huán)的神秘面紗。
一、進(jìn)程和線程
1.1進(jìn)程和線程的基本概念
要想了解JavaScript的運(yùn)行機(jī)制,首先得了解進(jìn)程和線程是什么
進(jìn)程(Process)是操作系統(tǒng)分配資源的基本單位
線程(Thread) 是程序執(zhí)行的最小單位,一個(gè)進(jìn)程中可以包含多個(gè)線程,共享內(nèi)存資源。
一個(gè)應(yīng)用至少一個(gè)進(jìn)程,每個(gè)進(jìn)程之間相互獨(dú)立,進(jìn)程之間共享內(nèi)存空間。
一個(gè)進(jìn)程至少一個(gè)線程,進(jìn)程開啟后會自動創(chuàng)建一個(gè)線程來運(yùn)行代碼,稱為主線程。要是程序需要同時(shí)執(zhí)行多塊代碼,就需要啟動動更多線程來執(zhí)行代碼,所以一個(gè)進(jìn)程可以包含多個(gè)進(jìn)程。
而JavaScript本身就是 運(yùn)行在瀏覽器中,而瀏覽器本身也是一個(gè)多進(jìn)程、多線程的復(fù)雜應(yīng)用
1.2 瀏覽器的多進(jìn)程架構(gòu)
現(xiàn)代瀏覽器(如 Chrome)采用多進(jìn)程架構(gòu),其主要進(jìn)程包括:
瀏覽器主進(jìn)程:負(fù)責(zé)地址欄、書簽欄、前進(jìn)后退、標(biāo)簽管理等 UI 操作。
網(wǎng)絡(luò)進(jìn)程:處理網(wǎng)絡(luò)資源的下載(如 HTML、CSS、JS、圖片等)。
渲染進(jìn)程:每打開一個(gè)標(biāo)簽頁,通常會分配一個(gè)獨(dú)立的渲染進(jìn)程,負(fù)責(zé)頁面渲染與 JavaScript 執(zhí)行。
GPU進(jìn)程:處理與圖形渲染相關(guān)的任務(wù),包括3D CSS效果、頁面UI的GPU加速繪制,以及視頻解碼等
插件進(jìn)程(Plugin Process):獨(dú)立運(yùn)行瀏覽器插件(如Flash、廣告攔截工具),防止插件崩潰影響瀏覽器或其他頁面
輔助進(jìn)程(根據(jù)場景啟動):跨頁面共享的JavaScript線程,獨(dú)立于渲染進(jìn)程管理
瀏覽器插件種類繁多,但我們需要重點(diǎn)掌握J(rèn)avaScript的運(yùn)行機(jī)制。這就要從關(guān)鍵的渲染進(jìn)程入手,它與前端頁面渲染密切相關(guān)。
1.3 渲染進(jìn)程中的主線程
渲染進(jìn)程內(nèi)部,核心是渲染主線程,它負(fù)責(zé):
解析 HTML,構(gòu)建 DOM 樹
解析 CSS,構(gòu)建 CSSOM
執(zhí)行 JavaScript
計(jì)算樣式、布局、繪制
JavaScript 的運(yùn)行環(huán)境就在這個(gè)主線程中。因此,JavaScript 是單線程執(zhí)行。
二、異步機(jī)制
JavaScript 只運(yùn)行在瀏覽器的渲染主線程中,渲染主線程就只有一個(gè),而這個(gè)線程既要負(fù)責(zé)腳本執(zhí)行,又要承擔(dān)頁面渲染、事件響應(yīng)等工作。試想一下,如果我們讓 JS 主線程一直執(zhí)行某個(gè)任務(wù)(比如復(fù)雜的循環(huán)),那其他任務(wù)(如用戶點(diǎn)擊)就無法響應(yīng),頁面也無法渲染更新,用戶體驗(yàn)會極差。
2.1 同步的代價(jià)
我們先來分析一段代碼
while (true) {
// 模擬高消耗運(yùn)算
}這段代碼是一個(gè)死循環(huán),它將讓頁面徹底卡死,因?yàn)橹骶€程被死循環(huán)阻塞,無法響應(yīng)任何操作。
2.2 異步機(jī)制的引入
為了防止主線程長時(shí)間阻塞,JavaScript 借助瀏覽器的其他線程(如計(jì)時(shí)器線程、網(wǎng)絡(luò)線程等)來異步處理任務(wù):
1.主線程將異步任務(wù)交由其他線程執(zhí)行(如 setTimeout 的計(jì)時(shí)由 Web APIs 中的 Timer 線程負(fù)責(zé))。
2.等任務(wù)完成后,將“回調(diào)函數(shù)”包裝成任務(wù)加入任務(wù)隊(duì)列。
3.主線程空閑后,從任務(wù)隊(duì)列中取出這些任務(wù)執(zhí)行。
這樣就實(shí)現(xiàn)了非阻塞的異步模型。
2.3 異步機(jī)制的解讀
JavaScript 是一門單線程語言,因?yàn)樗\(yùn)行在瀏覽器的渲染主線程上,而該線程是唯一的。
渲染主線程需要處理多項(xiàng)任務(wù),包括頁面渲染和執(zhí)行 JavaScript 代碼等。如果采用同步執(zhí)行方式,可能會導(dǎo)致主線程阻塞,進(jìn)而使消息隊(duì)列中的其他任務(wù)無法及時(shí)執(zhí)行。這不僅會造成主線程資源的浪費(fèi),還會導(dǎo)致頁面更新延遲,給用戶帶來卡頓的體驗(yàn)。
為此,瀏覽器采用異步機(jī)制來解決這個(gè)問題。當(dāng)遇到計(jì)時(shí)器、網(wǎng)絡(luò)請求、事件監(jiān)聽等任務(wù)時(shí),主線程會將這些任務(wù)交由其他線程處理,自身則繼續(xù)執(zhí)行后續(xù)代碼。待其他線程完成任務(wù)后,會將回調(diào)函數(shù)封裝成新任務(wù),添加到消息隊(duì)列末尾等待主線程調(diào)度執(zhí)行。
這種異步機(jī)制有效避免了瀏覽器阻塞,確保了單線程運(yùn)行的流暢性。
三、事件循環(huán)機(jī)制
3.1 事件循環(huán)(event loop)
事件循環(huán)是瀏覽器或 Node.js 中控制異步執(zhí)行的核心機(jī)制。它的本質(zhì)就是一個(gè)不斷循環(huán)的系統(tǒng):
while (true) {
// 1. 執(zhí)行一個(gè)宏任務(wù)(task)
// 2. 執(zhí)行完立即清空所有微任務(wù)(microtasks)
// 3. 如果需要更新界面,則進(jìn)行渲染
}每一次完整的循環(huán)稱為一個(gè)“tick”。在每個(gè) tick 中,主線程都會從任務(wù)隊(duì)列中取出一個(gè)宏任務(wù)執(zhí)行,并在其執(zhí)行完畢后立即清空微任務(wù)隊(duì)列。

3.2 任務(wù)隊(duì)列
現(xiàn)代瀏覽器不再僅使用“宏任務(wù)隊(duì)列 + 微任務(wù)隊(duì)列”的模型,而是根據(jù)任務(wù)來源將宏任務(wù)進(jìn)一步拆分成多個(gè)隊(duì)列,例如:
延時(shí)隊(duì)列:setTimeout, setInterval
用戶交互隊(duì)列:如 click、input 等事件
UI 渲染隊(duì)列:requestAnimationFrame
網(wǎng)絡(luò)事件隊(duì)列:fetch、xhr
這些消息隊(duì)列中的任務(wù)采用先進(jìn)先出機(jī)制,沒有獨(dú)立優(yōu)先級,但消息隊(duì)列本身具有優(yōu)先級劃分。
每個(gè)任務(wù)都有特定類型,同類型任務(wù)必須歸入同一隊(duì)列,不同類型任務(wù)可分配到不同隊(duì)列。在單次事件循環(huán)中,瀏覽器可根據(jù)實(shí)際情況從不同隊(duì)列中選取任務(wù)執(zhí)行。
瀏覽器必須維護(hù)專門的微隊(duì)列,該隊(duì)列中的任務(wù)享有最高執(zhí)行優(yōu)先級。
當(dāng)前主流隊(duì)列按優(yōu)先級排序?yàn)椋貉訒r(shí)隊(duì)列(中優(yōu)先級)、交互隊(duì)列(高優(yōu)先級)、微隊(duì)列(最高優(yōu)先級)。
3.3 瀏覽器中一輪事件循環(huán)的過程
根據(jù)上述對事件循環(huán)和異步任務(wù)的解釋,可以得出瀏覽器中的一輪事件的循環(huán)過程
取出一個(gè)宏任務(wù)執(zhí)行
執(zhí)行過程中可能注冊多個(gè)微任務(wù)(如 Promise.then)
執(zhí)行完宏任務(wù)后,立即依次執(zhí)行所有微任務(wù)
執(zhí)行 UI 更新與渲染
進(jìn)入下一輪循環(huán),重復(fù)上述流程
四、相關(guān)問題的解答
1. JS為什么會阻塞渲染
由于 JS 腳本的執(zhí)行與頁面渲染共用主線程,若 JS 長時(shí)間執(zhí)行,渲染任務(wù)就會延后。
例如以下同步阻塞代碼:
while(Date.now() < performance.now() + 2000) {}頁面會在 2 秒內(nèi)完全無法響應(yīng)。
此外,瀏覽器為了防止 JS 操作 DOM 導(dǎo)致的視覺中斷,在 JS 執(zhí)行期間通常會“暫停渲染”,等腳本執(zhí)行完畢再統(tǒng)一渲染頁面。
2. JS的計(jì)時(shí)器能做到真正的精確計(jì)時(shí)嘛
(1)計(jì)算機(jī)硬件和操作系統(tǒng)的限制
CPU時(shí)鐘精度問題,計(jì)算機(jī)沒有原子鐘,無法精確計(jì)算時(shí)間
操作系統(tǒng)提供的計(jì)時(shí) API(如 Windows 的 GetSystemTime)存在 1-15ms 的調(diào)用誤差。
(2)瀏覽器的引擎機(jī)制
嵌套超限補(bǔ)償,當(dāng)定時(shí)器嵌套層級超過 5 層時(shí)(如回調(diào)中再次調(diào)用 setTimeout),瀏覽器會強(qiáng)制增加 4ms 延遲:
// 假設(shè)這是第六次調(diào)用,調(diào)用開始產(chǎn)生額外延遲,即使設(shè)置延遲為0,也會有至少4ms延遲
setTimeout(function recur() {
setTimeout(recur, 0); // 實(shí)際延遲 ≥4ms
}, 0); 最小時(shí)間間隔,即使設(shè)置 setTimeout(fn, 0),實(shí)際延遲通常被限制為 1ms(Chrome)或 4ms(舊版瀏覽器)。
(3)事件循環(huán)調(diào)度延遲
主線程阻塞,當(dāng)同步代碼或長任務(wù)占用主線程時(shí),計(jì)時(shí)器回調(diào)必須等待。
隊(duì)列優(yōu)先級競爭,即使準(zhǔn)時(shí)到期,回調(diào)仍需等待:微任務(wù)隊(duì)列清空、更高優(yōu)先級的交互/動畫任務(wù)、當(dāng)前執(zhí)行棧為空。
總結(jié)
到此這篇關(guān)于JS的運(yùn)行機(jī)制之事件循環(huán)機(jī)制的文章就介紹到這了,更多相關(guān)JS事件循環(huán)機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- JavaScript運(yùn)行機(jī)制之事件循環(huán)(Event Loop)詳解
- 一文詳解JS中的事件循環(huán)機(jī)制
- 關(guān)于js的事件循環(huán)機(jī)制剖析
- 實(shí)例分析js事件循環(huán)機(jī)制
- JS的事件循環(huán)執(zhí)行機(jī)制詳解
- 一文詳解JavaScript中的事件循環(huán)(event?loop)機(jī)制
- 一篇文章帶你了解vue.js的事件循環(huán)機(jī)制
- 詳解JS瀏覽器事件循環(huán)機(jī)制
- 詳解JavaScript事件循環(huán)機(jī)制
- 實(shí)例詳解JS中的事件循環(huán)機(jī)制
相關(guān)文章
Bootstrap彈出框(Popover)被擠壓的問題小結(jié)
比較了下Bootstrap的popover和一些其它的開源項(xiàng)目,覺得Bootstrap的還算不錯(cuò)。在使用過程中遇到了一系列問題,下面小編給大家分享Bootstrap彈出框(Popover)被擠壓的問題小結(jié),需要的朋友參考下吧2017-07-07
JS和css實(shí)現(xiàn)檢測移動設(shè)備方向的變化并判斷橫豎屏幕
這篇文章主要介紹了JS和css實(shí)現(xiàn)檢測移動設(shè)備方向的變化并判斷橫豎屏幕,本文分別給出實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-05-05
JavaScript運(yùn)動框架 鏈?zhǔn)竭\(yùn)動到完美運(yùn)動(五)
這篇文章主要為大家詳細(xì)介紹了JavaScript運(yùn)動框架的第五部分,鏈?zhǔn)竭\(yùn)動到完美運(yùn)動,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
Javascript+CSS實(shí)現(xiàn)影像卷簾效果思路及代碼
Arcmap里面的一個(gè)卷簾效果肯定記憶很深刻,我也對這種比較炫的卷簾效果做了一下研究,現(xiàn)在給大家匯報(bào)下結(jié)果2014-10-10
Knockoutjs 學(xué)習(xí)系列(二)花式捆綁
這篇文章主要介紹了Knockoutjs 學(xué)習(xí)系列(二)花式捆綁 的相關(guān)資料,主要介紹了knockoutjs中各種綁定的使用方法,非常不錯(cuò)具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06

