1. 事件循環(huán)(Event Loop)
一些概念
1. 事件循環(huán)(Event Loop)
事件循環(huán)是瀏覽器(或類似環(huán)境如Node.js)中的一種機制酌呆,用于協(xié)調(diào)異步操作台颠、處理任務(wù)隊列以及調(diào)度各種類型的任務(wù)執(zhí)行瓤湘。其核心思想是維護一個或多個任務(wù)隊列(如宏任務(wù)隊列、微任務(wù)隊列)团搞,按照特定的順序?qū)⒋龍?zhí)行的任務(wù)推送到主線程中執(zhí)行蚕钦。主要流程如下:
宏任務(wù)(Macro Task):如
setTimeout
、setInterval
笆环、I/O
操作攒至、UI交互事件(如點擊、滾動)咧织、腳本執(zhí)行等嗓袱。每個宏任務(wù)執(zhí)行完畢后,檢查微任務(wù)隊列习绢。微任務(wù)(Micro Task):如
Promise.resolve/reject
渠抹、MutationObserver
、process.nextTick
(Node.js)等闪萄。在一個宏任務(wù)執(zhí)行完畢后梧却,所有微任務(wù)會立即執(zhí)行,直到微任務(wù)隊列清空败去,然后返回事件循環(huán)等待下一個宏任務(wù)放航。
2. 瀏覽器渲染(Rendering)
渲染是瀏覽器將DOM樹、CSSOM樹結(jié)合生成Render樹圆裕,計算布局(Layout)广鳍,最終繪制(Paint)到屏幕的過程。為了實現(xiàn)流暢的用戶體驗吓妆,瀏覽器通常以固定的幀率(如60fps赊时,即每16.67毫秒渲染一次)進行渲染。渲染過程包括:
布局(Layout/Reflow):計算元素尺寸行拢、位置等幾何信息祖秒。
繪制(Paint):根據(jù)布局信息在畫布上繪制文本、顏色、圖像等竭缝。
合成(Composite):對于層疊上下文中不同的圖層房维,進行合成操作以提高渲染效率。
3. 幀動畫(Frame Animation)
幀動畫是指通過連續(xù)改變頁面元素屬性抬纸,利用瀏覽器的渲染機制來模擬連續(xù)動畫效果咙俩。最常用的API是requestAnimationFrame
,它會將回調(diào)函數(shù)安排在下一次渲染前調(diào)用松却,確保動畫與瀏覽器的重繪同步暴浦,從而避免不必要的重排和重繪溅话,提高動畫性能晓锻。
4. 空閑回調(diào)(Idle Callbacks)
空閑回調(diào)通過requestIdleCallback
API實現(xiàn),允許開發(fā)者在瀏覽器主進程空閑時(即當前幀的所有工作完成后飞几,且在下一幀開始前)執(zhí)行低優(yōu)先級任務(wù)砚哆。這對于避免阻塞渲染和響應(yīng)用戶交互非常有用,尤其適合處理非緊急但可能消耗大量CPU資源的工作屑墨。
5. 關(guān)系與協(xié)作
事件循環(huán)與渲染:瀏覽器并不保證每一輪事件循環(huán)都伴隨渲染躁锁。渲染是基于幀率的,而事件循環(huán)則是持續(xù)進行的卵史。當有渲染需求(如DOM或樣式發(fā)生變化战转、
requestAnimationFrame
回調(diào)觸發(fā)等)時,瀏覽器會在合適的時機(通常是宏任務(wù)執(zhí)行間隙)進行渲染以躯。如果某次事件循環(huán)中沒有導(dǎo)致視覺變化的操作槐秧,或者瀏覽器判斷更新渲染不會帶來視覺上的改變括荡,可能會跳過此次渲染傻昙。事件循環(huán)與幀動畫:
requestAnimationFrame
回調(diào)被安排在下一次渲染前執(zhí)行,它們作為宏任務(wù)的一部分蚓庭,會在微任務(wù)之后址晕、下一次渲染之前進入事件循環(huán)膀懈。這樣,開發(fā)者可以在回調(diào)中更新動畫狀態(tài)谨垃,確保動畫與瀏覽器渲染幀同步启搂,避免動畫卡頓。事件循環(huán)與空閑回調(diào):
requestIdleCallback
提供的回調(diào)會在事件循環(huán)中所有高優(yōu)先級任務(wù)(如渲染刘陶、用戶交互響應(yīng)胳赌、宏任務(wù)、微任務(wù))完成后易核,在瀏覽器空閑時段內(nèi)被執(zhí)行匈织。如果當前幀沒有剩余時間或者瀏覽器上下文不可見(如標簽頁被切換到后臺),空閑回調(diào)可能會被延遲到下一個可用的空閑周期。渲染缀匕、幀動畫與性能:瀏覽器會盡可能保持幀率穩(wěn)定纳决,如果頁面性能無法維持60fps,會選擇較低的幀率(如30fps)以避免頻繁丟幀乡小。同時阔加,如果存在耗時過長的任務(wù)(如長時間運行的腳本),可能導(dǎo)致當前幀沒有時間進行渲染满钟,從而造成卡頓胜榔。合理使用
requestAnimationFrame
和避免阻塞主線程的任務(wù)可以幫助優(yōu)化動畫性能。
綜上所述湃番,事件循環(huán)夭织、渲染、幀動畫及空閑回調(diào)在瀏覽器環(huán)境中緊密協(xié)作吠撮,共同決定了網(wǎng)頁的視覺呈現(xiàn)尊惰、交互響應(yīng)和性能表現(xiàn)。理解它們之間的關(guān)系和運作機制泥兰,是進行高效前端開發(fā)和性能優(yōu)化的關(guān)鍵
先提幾個問題
假如顯示器的刷新頻率是 60Hz 瀏覽器也是 60Hz么弄屡? 假如是 瀏覽器會每隔16.67ms (1000ms/60)渲染一次么?
瀏覽器中的一幀指的是什么鞋诗?每一幀都干了那些工作膀捷?
假如有一個js任務(wù)執(zhí)行的時間超過了16.67ms會怎么樣?
事件循環(huán)中的隊列削彬,都有哪些全庸?
setTimeout(()=>{},1000) 這段代碼是什么意思,是否是1s后執(zhí)行吃警?
requestAnimationFrame requestIdleCallback 這兩個api是否知道糕篇?
HTML Standard 中的描述 https://html.spec.whatwg.org/multipage/webappapis.html#task-queue
事件循環(huán)定義
事件循環(huán)是瀏覽器(或類似環(huán)境如Node.js)中的一種機制,用于協(xié)調(diào)異步操作酌心、處理任務(wù)隊列以及調(diào)度各種類型的任務(wù)執(zhí)行拌消。其核心思想是維護一個或多個任務(wù)隊列(如宏任務(wù)隊列、微任務(wù)隊列)安券,按照特定的順序?qū)⒋龍?zhí)行的任務(wù)推送到主線程中執(zhí)行墩崩。
To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. Each agent has an associated event loop, which is unique to that agent.
為了協(xié)調(diào)事件、用戶交互侯勉、腳本鹦筹、渲染、網(wǎng)絡(luò)請求 等等址貌,用戶代理(即瀏覽器)必須使用在本節(jié)中描述的事件循環(huán)铐拐。每個代理都有一個關(guān)聯(lián)的事件循環(huán)徘键,這對于該代理來說是唯一的。
規(guī)范中明確提到了“事件循環(huán)”這一術(shù)語遍蟋,并描述了其基本作用:
An event loop is a construct that handles events and callbacks in a web application. Each event loop has one or more task queues.
這里指出事件循環(huán)是一種處理web應(yīng)用程序中事件和回調(diào)的構(gòu)造吹害,每個事件循環(huán)都關(guān)聯(lián)有一個或多個任務(wù)隊列。
2. 任務(wù)隊列(Task Queues)
規(guī)范詳細定義了任務(wù)隊列的概念:
A task queue is an ordered set of tasks. Unless otherwise specified, a user agent must use the first-in-first-out ordering.
任務(wù)隊列是一個有序的任務(wù)集合虚青,除非另有規(guī)定它呀,用戶代理(即瀏覽器)應(yīng)采用先進先出(FIFO)的順序處理任務(wù)。
3. 任務(wù)(Tasks)
規(guī)范中定義了任務(wù)(Task)為事件循環(huán)中可執(zhí)行的基本單位:
A task is a unit of execution that is part of a task queue. Most tasks are callback functions from API calls, events, or other user actions.
任務(wù)是構(gòu)成任務(wù)隊列的執(zhí)行單元棒厘。大多數(shù)任務(wù)來源于API調(diào)用纵穿、事件或其他用戶操作的回調(diào)函數(shù)。
4. 事件循環(huán)過程
規(guī)范描述了事件循環(huán)的基本處理流程:
1.選擇一個待處理任務(wù)隊列:從所有關(guān)聯(lián)的任務(wù)隊列中選擇一個(如果沒有可選的任務(wù)隊列奢人,則進入等待狀態(tài))谓媒。
2.取出并執(zhí)行隊列中的第一個任務(wù):取出所選隊列中的第一個任務(wù)并執(zhí)行。
- 重復(fù)步驟1和2:直到所選隊列為空达传,然后返回第一步選擇下一個任務(wù)隊列篙耗。
5. 微任務(wù)(Microtasks)
盡管上述內(nèi)容主要描述了常規(guī)任務(wù)隊列,規(guī)范中還提到了微任務(wù)(Microtasks)和微任務(wù)隊列(Microtask Queue)宪赶,它們是事件循環(huán)中更為精細的調(diào)度單位,具有更高的優(yōu)先級脯燃。微任務(wù)通常在每個任務(wù)(包括事件處理程序搂妻、腳本執(zhí)行等)執(zhí)行完畢后立即執(zhí)行,直至微任務(wù)隊列清空辕棚。微任務(wù)常用于實現(xiàn)Promise欲主、MutationObserver等API的異步行為。
This specification does not mandate any particular model for selecting rendering opportunities. But for example, if the browser is attempting to achieve a 60Hz refresh rate, then rendering opportunities occur at a maximum of every 60th of a second (about 16.7ms). If the browser finds that a navigable is not able to sustain this rate, it might drop to a more sustainable 30 rendering opportunities per second for that navigable, rather than occasionally dropping frames. Similarly, if a navigable is not visible, the user agent might decide to drop that page to a much slower 4 rendering opportunities per second, or even less.
這個規(guī)范沒有強制任何特定的模型來選擇渲染機會逝嚎。但是扁瓢,例如,如果瀏覽器試圖實現(xiàn)60Hz刷新率补君,那么 渲染機會最多每60秒發(fā)生一次(約16.7ms)引几。如果瀏覽器發(fā)現(xiàn)一個網(wǎng)頁(tab)不能維持這個速率,它可能會為那個tab網(wǎng)頁下降到更可持續(xù)的每秒30次渲染機會挽铁,而不是偶爾丟棄幀伟桅。同樣,如果一個導(dǎo)航不可見叽掘,用戶代理可能會決定將該頁面降到更慢的每秒4次渲染機會楣铁,甚至更少。
事件循環(huán)與渲染:瀏覽器并不保證每一輪事件循環(huán)都伴隨渲染更扁。渲染是基于幀率的盖腕,而事件循環(huán)則是持續(xù)進行的赫冬。當有渲染需求(如DOM或樣式發(fā)生變化、
requestAnimationFrame
回調(diào)觸發(fā)等)時溃列,瀏覽器會在合適的時機(通常是宏任務(wù)執(zhí)行間隙)進行渲染面殖。如果某次事件循環(huán)中沒有導(dǎo)致視覺變化的操作,或者瀏覽器判斷更新渲染不會帶來視覺上的改變哭廉,可能會跳過此次渲染脊僚。事件循環(huán)與幀動畫:
requestAnimationFrame
回調(diào)被安排在下一次渲染前執(zhí)行,它們作為宏任務(wù)的一部分遵绰,會在微任務(wù)之后辽幌、下一次渲染之前進入事件循環(huán)。這樣椿访,開發(fā)者可以在回調(diào)中更新動畫狀態(tài)乌企,確保動畫與瀏覽器渲染幀同步,避免動畫卡頓成玫。事件循環(huán)與空閑回調(diào):
requestIdleCallback
提供的回調(diào)會在事件循環(huán)中所有高優(yōu)先級任務(wù)(如渲染加酵、用戶交互響應(yīng)、宏任務(wù)哭当、微任務(wù))完成后猪腕,在瀏覽器空閑時段內(nèi)被執(zhí)行。如果當前幀沒有剩余時間或者瀏覽器上下文不可見(如標簽頁被切換到后臺)钦勘,空閑回調(diào)可能會被延遲到下一個可用的空閑周期陋葡。渲染、幀動畫與性能:瀏覽器會盡可能保持幀率穩(wěn)定彻采,如果頁面性能無法維持60fps腐缤,會選擇較低的幀率(如30fps)以避免頻繁丟幀。同時肛响,如果存在耗時過長的任務(wù)(如長時間運行的腳本)岭粤,可能導(dǎo)致當前幀沒有時間進行渲染,從而造成卡頓特笋。合理使用
requestAnimationFrame
和避免阻塞主線程的任務(wù)可以幫助優(yōu)化動畫性能剃浇。
事件處理與頁面更新:
- 用戶交互(如鼠標點擊、鍵盤輸入雹有、滾動等)產(chǎn)生事件偿渡,這些事件被操作系統(tǒng)傳遞給瀏覽器,經(jīng)由事件隊列進入事件循環(huán)霸奕。
2.事件循環(huán)檢測到事件隊列中有待處理事件時溜宽,取出事件并交由渲染引擎的事件處理模塊處理。
3.事件處理可能會觸發(fā)JavaScript腳本執(zhí)行(如onclick
事件的回調(diào)函數(shù))质帅,此時事件循環(huán)暫停渲染引擎的工作适揉,將控制權(quán)交給JavaScript引擎(如V8)執(zhí)行腳本留攒。
4.腳本執(zhí)行過程中可能修改DOM結(jié)構(gòu)或CSS樣式,這些變化被瀏覽器記錄下來嫉嘀,等待后續(xù)的渲染更新炼邀。
- 腳本執(zhí)行完成后,事件循環(huán)恢復(fù)對渲染引擎的控制剪侮,渲染引擎檢查是否有待處理的DOM變更拭宁,并根據(jù)需要觸發(fā)回流(layout)和重繪(paint)過程,更新頁面內(nèi)容瓣俯。
6.更新完成后杰标,渲染引擎通知事件循環(huán)工作已完成,事件循環(huán)繼續(xù)處理隊列中的下一個事件彩匕。
每一幀的時間通常是指瀏覽器兩次渲染之間的時間間隔腔剂。 在瀏覽器渲染過程中,為了形成連續(xù)流暢的視覺效果驼仪,屏幕內(nèi)容通常以一定的頻率(幀率)進行更新掸犬。這一頻率通常以每秒的幀數(shù)(Frames Per Second, FPS)來衡量,常見的目標幀率為60 FPS绪爸,即每一幀的時間間隔大約為16.67毫秒(1秒 ÷ 60幀 ≈ 16.67 ms)湾碎。
事件循環(huán)的工作原理和執(zhí)行過程
1.事件循環(huán)的目的:事件循環(huán)是為了處理用戶交互、網(wǎng)絡(luò)請求毡泻、定時器等事件胜茧,以及執(zhí)行與這些事件相關(guān)的回調(diào)函數(shù)。
2.宏任務(wù)(Macrotasks)和微任務(wù)(Microtasks):事件循環(huán)中的任務(wù)分為宏任務(wù)和微任務(wù)兩種類型仇味。宏任務(wù)包括腳本執(zhí)行、用戶交互事件雹顺、定時器等丹墨,而微任務(wù)則包括 Promise 的回調(diào)函數(shù)、MutationObserver 的回調(diào)函數(shù)等嬉愧。
3.宏任務(wù)隊列和微任務(wù)隊列:事件循環(huán)維護了一個宏任務(wù)隊列和一個微任務(wù)隊列贩挣。當事件觸發(fā)時,相關(guān)的任務(wù)會被添加到相應(yīng)的隊列中没酣。
4.事件循環(huán)的執(zhí)行過程:事件循環(huán)的執(zhí)行過程是一個循環(huán)迭代的過程王财。在每一次迭代中,事件循環(huán)會執(zhí)行以下步驟:
* 從宏任務(wù)隊列中取出一個宏任務(wù)并執(zhí)行裕便。
* 執(zhí)行過程中绒净,如果產(chǎn)生了微任務(wù),將其添加到微任務(wù)隊列中偿衰。
* 執(zhí)行微任務(wù)隊列中的所有微任務(wù)挂疆。
*
更新渲染(如果需要)改览。
*
重復(fù)以上步驟,直到宏任務(wù)隊列和微任務(wù)隊列都為空缤言。
微任務(wù)的優(yōu)先級:微任務(wù)具有比宏任務(wù)更高的優(yōu)先級宝当。也就是說,在每次迭代中胆萧,事件循環(huán)會首先執(zhí)行微任務(wù)隊列中的所有微任務(wù)庆揩,然后再執(zhí)行宏任務(wù)隊列中的下一個宏任務(wù)。
事件循環(huán)的終止條件:事件循環(huán)會一直執(zhí)行跌穗,直到宏任務(wù)隊列和微任務(wù)隊列都為空订晌,或者被終止。
(1)所有同步任務(wù)都在主線程上執(zhí)行瞻离,形成一個執(zhí)行棧(execution context stack)腾仅。
(2)主線程之外,還存在一個"任務(wù)隊列"(task queue)套利。只要異步任務(wù)有了運行結(jié)果推励,就在"任務(wù)隊列"之中放置一個事件。
(3)一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢肉迫,系統(tǒng)就會讀取"任務(wù)隊列"验辞,看看里面有哪些事件。那些對應(yīng)的異步任務(wù)喊衫,于是結(jié)束等待狀態(tài)跌造,進入執(zhí)行棧,開始執(zhí)行族购。
(4)主線程不斷重復(fù)上面的第三步壳贪。
每一幀的時間通常是指瀏覽器兩次渲染之間的時間間隔。 在瀏覽器渲染過程中寝杖,為了形成連續(xù)流暢的視覺效果违施,屏幕內(nèi)容通常以一定的頻率(幀率)進行更新。這一頻率通常以每秒的幀數(shù)(Frames Per Second, FPS)來衡量瑟幕,常見的目標幀率為60 FPS磕蒲,即每一幀的時間間隔大約為16.67毫秒(1秒 ÷ 60幀 ≈ 16.67 ms)。
幀的概念與瀏覽器渲染流程:
-
渲染一幀:瀏覽器渲染一幀的過程包括但不限于以下步驟:
布局計算(Layout/Reflow):計算Render樹中元素的幾何位置和尺寸只盹。
繪制(Paint):將元素的視覺信息(如顏色辣往、圖像、文本)繪制到一塊離屏畫布上殖卑。
合成(Composite):如果有多個圖層(Layer)站削,將各層合并成單一的顯示列表,并可能利用GPU加速進行合成操作懦鼠。
提交(Compositor Commit):將最終的顯示列表提交給操作系統(tǒng)钻哩,由硬件顯示設(shè)備顯示到屏幕上屹堰。
幀間隔:瀏覽器完成一幀渲染后,會等待下一次垂直同步信號(VSync)到來街氢,標志著新的一幀開始扯键。在這個間隔時間內(nèi),瀏覽器可以處理JavaScript任務(wù)珊肃、更新DOM/CSSOM荣刑、處理用戶輸入事件、調(diào)度網(wǎng)絡(luò)請求等伦乔。然后厉亏,瀏覽器開始準備渲染下一幀,重復(fù)上述渲染流程烈和。
幀率與平滑度:
保持穩(wěn)定的高幀率(如60 FPS)有助于提供平滑爱只、響應(yīng)迅速的用戶體驗。如果瀏覽器不能在每一幀的時間間隔內(nèi)完成所有渲染工作招刹,導(dǎo)致幀率下降(如低于30 FPS)恬试,用戶可能會感知到卡頓或掉幀現(xiàn)象。為了達到高幀率疯暑,瀏覽器會盡可能優(yōu)化渲染流水線训柴,如使用分層渲染、硬件加速妇拯、預(yù)渲染等技術(shù)幻馁,同時開發(fā)者也需要編寫高效的JavaScript代碼,避免長時間阻塞主線程越锈。
總結(jié)來說仗嗦,每一幀的時間確實是瀏覽器兩次渲染之間的時間間隔。這個時間間隔受到顯示器刷新率甘凭、瀏覽器渲染性能儒将、頁面復(fù)雜度、JavaScript執(zhí)行效率等多種因素的影響对蒲。瀏覽器努力在每一幀的時間內(nèi)完成所有必要的渲染工作,以提供流暢的視覺體驗贡翘。
只要異步任務(wù)有了運行結(jié)果蹈矮,就在"任務(wù)隊列"之中放置一個事件。(https://ruanyifeng.com/blog/2014/10/event-loop.html)
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Event_loop
https://developer.mozilla.org/zh-CN/docs/Web/API/window/requestAnimationFrame
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback
https://html.spec.whatwg.org/multipage/webappapis.html#event-loops
https://zhuanlan.zhihu.com/p/142742003
https://juejin.cn/post/7323017937752719369