跟著whatwg看一遍事件循環(huán)

前言

對于單線程來說糯钙,事件循環(huán)可以說是重中之重了,它為任務(wù)分配不同的優(yōu)先級退腥,井然有序的調(diào)度任岸。讓js解析,用戶交互狡刘,頁面渲染等互不沖突享潜,各司其職。

我們書寫的代碼無時無刻都在和事件循環(huán)打交道嗅蔬,要想寫出更流暢米碰,我們就必須深入了解事件循環(huán),下面我們將從規(guī)范中翻譯和解讀整個流程购城。

以下內(nèi)容來自whatwg文檔吕座,均為個人理解,若有不對瘪板,煩請指出吴趴,我會第一時間修改,避免誤導(dǎo)他人侮攀!

正文

為了協(xié)調(diào)用戶操作锣枝,js執(zhí)行,頁面渲染兰英,網(wǎng)絡(luò)請求等事件撇叁,每個宿主中,存在事件循環(huán)這樣的角色畦贸,并且該角色在當(dāng)前宿主中是唯一的陨闹。

簡單解釋一下宿主:宿主是一個ECMAScript執(zhí)行上下文,一般包含執(zhí)行上下文棧薄坏,運行時執(zhí)行環(huán)境趋厉,宿主記錄和一個執(zhí)行線程,除了這個執(zhí)行線程外胶坠,其他的專屬于當(dāng)前宿主君账。例如,某些瀏覽器在不同的tabs使用同一個執(zhí)行線程沈善。

不僅如此乡数,事件循環(huán)又存于在各個不同場景椭蹄,有瀏覽器環(huán)境下的,worker環(huán)境下的和Worklet環(huán)境下的净赴。

Worklet是一個輕量級的web worker塑娇,可以讓開發(fā)者訪問更底層的渲染工作線,也就是說你可以通過Worklet去干預(yù)瀏覽器的渲染環(huán)境劫侧。

提到了worklet埋酬,那就順便看一個例子(需開啟服務(wù),不要以file協(xié)議運行)烧栋,通過這個例子写妥,可以看到事件循環(huán)不同階段觸發(fā)了什么鉤子函數(shù):

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
            .fancy {
                background-image: paint(headerHighlight);
                display: layout(sample-layout);
                background-color: green;
            }
        </style>
    </head>
    <body>
        <h1 class="fancy">My Cool Header</h1>
        <script>
            console.log('開始');
            CSS.paintWorklet.addModule('./paint.js');
            CSS.layoutWorklet.addModule('./layout.js');

            requestAnimationFrame(() => {
                console.log('requestAnimationFrame');
            });
            Promise.resolve().then(() => {
                console.log('微任務(wù)');
            });
            setTimeout(function () {
                document.querySelector('.fancy').style.height = '150px';
                ('translateZ(0)');

                Promise.resolve().then(() => {
                    console.log('新一輪的微任務(wù)');
                });
                requestAnimationFrame(() => {
                    console.log('新一輪的requestAnimationFrame');
                });
            }, 2000);
            console.log(2);
        </script>
    </body>
</html>

// paint.js
registerPaint(
    'headerHighlight',
    class {
        static get contextOptions() {
            console.log('contextOptions');
            return {alpha: true};
        }

        paint(ctx) {
            console.log('paint函數(shù)');
        }
    }
);

// ==========================分割線

// layout.js
registerLayout(
    'sample-layout',
    class {
        async intrinsicSizes(children, edges, styleMap) {}

        async layout(children, edges, constraints, styleMap, breakToken) {
            console.log('layout階段');
        }
    }
);
image

事件循環(huán)有一個或多個Task隊列,每個Task隊列都是Task的一個集合审姓。其中Task不是指我們的某個函數(shù)珍特,而是一個上下文環(huán)境,結(jié)構(gòu)如下:

  • step:一系列任務(wù)將要執(zhí)行的步驟
  • source:任務(wù)來源魔吐,常用來對相關(guān)任務(wù)進(jìn)行分組和系列化
  • document:與當(dāng)前任務(wù)相關(guān)的document對象扎筒,如果是非window環(huán)境則為null
  • 環(huán)境配置對象:在任務(wù)期間追蹤記錄任務(wù)狀態(tài)

這里的Task隊列不是Task,是一個集合酬姆,因為取出一個Task隊列中的Task是選擇一個可執(zhí)行的Task嗜桌,而不是出隊操作。

微任務(wù)隊列是一個入對出對的隊列辞色。

這里說明一下骨宠,Task隊列為什么有多個爱只,因為不同的Task隊列有不同的優(yōu)先級穆刻,進(jìn)而進(jìn)行次序排列和調(diào)用熊尉,有沒有感覺react的fiber和這個有點類似浊洞?

舉個例子,Task隊列可以是專門負(fù)責(zé)鼠標(biāo)和鍵盤事件的盾似,并且賦予鼠標(biāo)鍵盤隊列較高的優(yōu)先級挽唉,以便及時響應(yīng)用戶操作塘秦。另一個Task隊列負(fù)責(zé)其他任務(wù)源建蹄。不過也不要餓死任何一個task碌更,這個后續(xù)處理模型中會介紹。

Task封裝了負(fù)責(zé)以下任務(wù)的算法:

  • Events: 由專門的Task在特定的EventTarget(一個具有監(jiān)聽訂閱模式列表的對象)上分發(fā)事件對象
  • Parsing: html解析器標(biāo)記一個或多個字節(jié)躲撰,并處理所有生成的結(jié)果token
  • Callbacks: 由專門的Task觸發(fā)回調(diào)函數(shù)
  • Using a resource: 當(dāng)該算法獲取資源的時候针贬,如果該階段是以非阻塞方式發(fā)生,那么一旦部分或者全部資源可用拢蛋,則由Task進(jìn)行后續(xù)處理
  • Reacting to DOM manipulation: 通過dom操作觸發(fā)的任務(wù),例如插入一個節(jié)點到document

事件循環(huán)有一個當(dāng)前運行中的Task蔫巩,可以為null谆棱,如果是null的話快压,代表著可以接受一個新的Task(新一輪的步驟)。

事件循環(huán)有微任務(wù)隊列垃瞧,默認(rèn)為空蔫劣,其中的任務(wù)由微任務(wù)排隊算法創(chuàng)建。

事件循環(huán)有一個執(zhí)行微任務(wù)檢查點个从,默認(rèn)為false脉幢,用來防止微任務(wù)死循環(huán)。

微任務(wù)排隊算法:

  1. 如果未提供event loop嗦锐,設(shè)置一個隱式event loop嫌松。
  2. 如果未提供document,設(shè)置一個隱式document.
  3. 創(chuàng)建一個Task作為新的微任務(wù)
  4. 設(shè)置setp奕污、source萎羔、document到新的Task上
  5. 設(shè)置Task的環(huán)境配置對象為空集
  6. 添加到event loop的微任務(wù)隊列中

微任務(wù)檢查算法:

  1. 如果微任務(wù)檢查標(biāo)志為true,直接return
  2. 設(shè)置微任務(wù)檢查標(biāo)志為true
  3. 如果微任務(wù)隊里不為空(也就是說微任務(wù)添加的微任務(wù)也會在這個循環(huán)中出現(xiàn)碳默,直到微任務(wù)隊列為空):
    1. 從微任務(wù)隊列中找出最老的任務(wù)(防餓死)
    2. 設(shè)置當(dāng)前執(zhí)行任務(wù)為這個最老的任務(wù)
    3. 執(zhí)行
    4. 重置當(dāng)前執(zhí)行任務(wù)為null
  4. 通知環(huán)境配置對象的promise進(jìn)行reject操作
  5. 清理indexdb事務(wù)(不太明白這一步贾陷,如果有讀者了解,煩請點撥一下)
  6. 設(shè)置微任務(wù)檢查標(biāo)志為false

處理模型

event loop會按照下面這些步驟進(jìn)行調(diào)度:

  1. 找到一個可執(zhí)行的Task隊列嘱根,如果沒有則跳轉(zhuǎn)到下面的微任務(wù)步驟
  2. 讓最老的Task作為Task隊列中第一個可執(zhí)行的Task髓废,并將其移除
  3. 將最老的Task作為event loop的可執(zhí)行Task
  4. 記錄任務(wù)開始時間點
  5. 執(zhí)行Task中的setp對應(yīng)的步驟(上文中Task結(jié)構(gòu)中的step)
  6. 設(shè)置event loop的可執(zhí)行任務(wù)為null
  7. 執(zhí)行微任務(wù)檢查算法
  8. 設(shè)置hasARenderingOpportunity(是否可以渲染的flag)為false
  9. 記住當(dāng)前時間點
  10. 通過下面步驟記錄任務(wù)持續(xù)時間
    1. 設(shè)置頂層瀏覽器環(huán)境為空
    2. 對于每個最老Task的腳本執(zhí)行環(huán)境配置對象,設(shè)置當(dāng)前的頂級瀏覽器上下文到其上
    3. 報告消耗過長的任務(wù)该抒,并附帶開始時間瓦哎,結(jié)束時間,頂級瀏覽器上下文和當(dāng)前Task
  11. 如果在window環(huán)境下柔逼,會根據(jù)硬件條件決定是否渲染蒋譬,比如刷新率,頁面性能愉适,頁面是否在后臺犯助,不過渲染會定期出現(xiàn),避免頁面卡頓维咸。值得注意的是剂买,正常的刷新率為60hz,大概是每秒60幀癌蓖,大約16.7ms每幀瞬哼,如果當(dāng)前瀏覽器環(huán)境不支持這個刷新率的話,會自動降為30hz租副,而不是丟幀坐慰。而李蘭其在后臺的時候,聰明的瀏覽器會將這個渲染時機降為每秒4幀甚至更低用僧,事件循環(huán)也會減少(這就是為什么我們可以用setInterval來判斷時候能打開其他app的判斷依據(jù)的原因)结胀。如果能渲染的話會設(shè)置hasARenderingOpportunity為true赞咙。

除此之外,還會在觸發(fā)resize糟港、scroll攀操、建立媒體查詢、運行css動畫等秸抚,也就是說瀏覽器幾乎大部分用戶操作都發(fā)生在事件循環(huán)中速和,更具體點是事件循環(huán)中的ui render部分。之后會進(jìn)行requestAnimationFrame和IntersectionObserver的觸發(fā)剥汤,再之后是ui渲染

  1. 如果下面條件都成立颠放,那么執(zhí)行空閑階段算法,對于開發(fā)者來說就是調(diào)用window.requestIdleCallback方法
    1. 在window環(huán)境下
    2. event loop中沒有活躍的Task
    3. 微任務(wù)隊列為空
    4. hasARenderingOpportunity為false

借鑒網(wǎng)上的一張圖來粗略表示下整個流程

image

小結(jié)

上面就是整個事件循環(huán)的流程秀姐,瀏覽器就是按照這個規(guī)則一遍遍的執(zhí)行慈迈,而我們要做的就是了解并適應(yīng)這個規(guī)則,讓瀏覽器渲染出性能更高的頁面省有。

比如:

  1. 非首屏相關(guān)性能打點可以放到idle callback中執(zhí)行痒留,減少對頁面性能的損耗
  2. 微任務(wù)中遞歸添加微任務(wù)會導(dǎo)致頁面卡死,而不是隨著事件循環(huán)一輪輪的執(zhí)行
  3. 更新元素布局的最好時機是在requestAnimateFrame中
  4. 盡量避免頻繁獲取元素布局信息蠢沿,因為這會觸發(fā)強制layout(哪些屬性會導(dǎo)致強制layout伸头?),影響頁面性能
  5. 事件循環(huán)有多個任務(wù)隊列舷蟀,他們互不沖突恤磷,但是用戶交互相關(guān)的優(yōu)先級更高
  6. resize、scroll等會伴隨事件循環(huán)中ui渲染觸發(fā)野宜,而不是根據(jù)我們的滾動觸發(fā)扫步,換句話說,這些操作自帶節(jié)流
  7. 等等匈子,歡迎補充

最后感謝大家閱讀河胎,歡迎一起探討!

提前祝大家端午節(jié)nb

參考

composite

深入探究 eventloop 與瀏覽器渲染的時序問題

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末虎敦,一起剝皮案震驚了整個濱河市游岳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌其徙,老刑警劉巖胚迫,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異唾那,居然都是意外死亡访锻,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朗若,“玉大人恼五,你說我怎么就攤上這事昌罩】扌福” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵茎用,是天一觀的道長遣总。 經(jīng)常有香客問我,道長轨功,這世上最難降的妖魔是什么旭斥? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮古涧,結(jié)果婚禮上垂券,老公的妹妹穿的比我還像新娘。我一直安慰自己羡滑,他們只是感情好菇爪,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著柒昏,像睡著了一般凳宙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上职祷,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天氏涩,我揣著相機與錄音,去河邊找鬼有梆。 笑死是尖,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的泥耀。 我是一名探鬼主播饺汹,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼爆袍!你這毒婦竟也來了首繁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤陨囊,失蹤者是張志新(化名)和其女友劉穎弦疮,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜘醋,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡胁塞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啸罢。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡编检,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出扰才,到底是詐尸還是另有隱情允懂,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布衩匣,位于F島的核電站蕾总,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏琅捏。R本人自食惡果不足惜生百,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柄延。 院中可真熱鬧蚀浆,春花似錦、人聲如沸搜吧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赎败。三九已至秕衙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間僵刮,已是汗流浹背据忘。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留搞糕,地道東北人勇吊。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像窍仰,于是被迫代替她去往敵國和親汉规。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容