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

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

