自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

EventLoop = TaskQueue + RenderQueue,你看明白了嗎?

開發(fā) 前端
在整個過程中,「瀏覽器的角色是中介」,它提供了執(zhí)行API的環(huán)境和必要的安全措施。這些API讓W(xué)eb應(yīng)用可以像本地應(yīng)用一樣豐富和強大,同時仍然運行在瀏覽器這個相對安全的沙箱環(huán)境中。

前言

在最近的工作和學(xué)習(xí)中,有一個詞總是在眼前揮之不去--EventLoop。而在之前,其實我們講過相關(guān)的內(nèi)容,Event Loop 可視化解析

Event Loop圖片

上文我們從偏JS調(diào)用機制的角度分析了,調(diào)用棧(Call Stack)/宏任務(wù)隊列 (Task Queue)和微任務(wù)隊列 (Microtask Queue)他們之間的關(guān)系和他們是如何協(xié)同合作的。并且,舉了很多例子,用可視化的方式講解它們?nèi)绾喂ぷ鞯摹?/p>

而今天,我們從瀏覽器內(nèi)部的實現(xiàn)細節(jié)來談?wù)凟ventLoop是如何從接受任務(wù)到渲染出對應(yīng)頁面的。

也就是下圖中所涉及到的各個重要節(jié)點。在閱讀完本文后,希望大家能對下面有一個清晰的認知。

圖片圖片

好了,天不早了,干點正事哇。

我們能所學(xué)到的知識點

  1. 前置知識點
  2. 事件循環(huán)(Event Loop)
  3. 任務(wù)隊列/微任務(wù)隊列/調(diào)用棧
  4. 在渲染隊列中執(zhí)行的是什么?
  5. EventLoop模型

1. 前置知識點

「前置知識點」,只是做一個概念的介紹,不會做深度解釋。因為,這些概念在下面文章中會有出現(xiàn),為了讓行文更加的順暢,所以將本該在文內(nèi)的概念解釋放到前面來?!溉绻蠹覍@些概念熟悉,可以直接忽略」同時,由于閱讀我文章的群體有很多,所以有些知識點可能「我視之若珍寶,爾視只如草芥,棄之如敝履」。以下知識點,請「酌情使用」。

頁面刷新術(shù)語

我們在頁面是如何生成的(宏觀角度)一文中提到過這些指標,這里就拿來主義了。

  • 「屏幕刷新頻率」

一秒內(nèi)屏幕刷新的次數(shù)(一秒內(nèi)顯示了多少幀的圖像),單位 Hz(赫茲),如常見的 60 Hz?!杆⑿骂l率取決于硬件的固定參數(shù)」(不會變的)。

  • 「逐行掃描」
  • 顯示器并不是一次性將畫面顯示到屏幕上,而是「從左到右邊,從上到下逐行掃描」,順序顯示整屏的一個個像素點,不過這一過程快到人眼無法察覺到變化。

  • 以 60 Hz 刷新率的屏幕為例,這一過程即 1000 / 60 ≈ 16ms。

  • 當掃描完一個屏幕后,設(shè)備需要「重新回到第一行」以進入下一次的循環(huán),此時有一段時間空隙,稱為VerticalBlanking Interval(VBI)。

  • 「幀率 (Frame Rate)」

  • 表示 「GPU 在一秒內(nèi)繪制操作的幀數(shù)」,如 60 fps,即每秒鐘GPU最多繪制 60 幀畫面。

  • 幀率是「動態(tài)變化」的,例如當畫面靜止時,GPU 是沒有繪制操作的,屏幕刷新的還是buffer中的數(shù)據(jù),即GPU最后操作的幀數(shù)據(jù)。

  • 「畫面撕裂(tearing)」

  • 一個屏幕內(nèi)的數(shù)據(jù)來自2個不同的幀,畫面會出現(xiàn)撕裂感。

測試幀率

我們可以借助requestAnimationFrame通過每個測量前后幀發(fā)生的時間間隔,來從側(cè)面查看本地瀏覽器幀率。

const checkRequestAnimationDiff = () => {
    let prev;
    function call() {
        requestAnimationFrame((timestamp) => {
            if (prev) {
                console.log(timestamp - prev); 
                // 應(yīng)該大約是60FPS的16.6毫秒
            }
            prev = timestamp;
            call();
        });
    }
    call();
}
checkRequestAnimationDiff();

隨意打開一個網(wǎng)站,并將上述代碼貼到devtool-Console運行。

下面是,我們在React-官網(wǎng)[1]中實驗的結(jié)果。

圖片圖片

從輸出結(jié)果來看,雖然結(jié)果不是唯一,但是它們的值都穩(wěn)定在16.67~16.68。和我們60fps是吻合的。

WebAPI

WebAPI工作的原理依賴于瀏覽器作為宿主環(huán)境來提供和執(zhí)行這些API。在Web開發(fā)中,我們通常指的WebAPI是「瀏覽器內(nèi)置的API」,它們允許開發(fā)者利用JavaScript與瀏覽器的功能進行交互。

APIs

描述

網(wǎng)絡(luò)請求

(Network Requests)

使用XMLHttpRequestfetch API,可以發(fā)起異步的HTTP請求到服務(wù)器,并在不刷新頁面的情況下獲取或發(fā)送數(shù)據(jù)。


DOM操作

(DOM Manipulation)

瀏覽器提供了一套DOM API,允許JavaScript訪問和操作頁面上的元素。比如,可以添加、刪除或更改元素,或者修改元素的樣式和內(nèi)容。

事件處理

(Event Handling)

WebAPI允許注冊事件處理程序來響應(yīng)用戶行為(如點擊、滑動)或瀏覽器事件(如頁面加載、窗口尺寸變化)。

存儲機制

(Storage Mechanisms)

瀏覽器提供了如localStorage、sessionStorageIndexedDB等API,可以在用戶的設(shè)備上存儲數(shù)據(jù)。

設(shè)備API

(Device APIs)

可以訪問設(shè)備的硬件,如攝像頭、麥克風(fēng)、地理位置等,這通常通過navigator對象暴露的API實現(xiàn)。

圖形和動畫

(Graphics & Animation)

CanvasWebGL API允許在網(wǎng)頁上繪制二維和三維圖形。requestAnimationFrame為動畫提供了一個優(yōu)化的循環(huán)機制。

性能監(jiān)控

(Performance Monitoring)

Performance API提供了獲取瀏覽器性能相關(guān)數(shù)據(jù)的接口,幫助開發(fā)者監(jiān)控和優(yōu)化網(wǎng)頁性能。

其他API

(Other APIs)

還有諸如Web Audio API、WebRTCWebSocket等,使得在網(wǎng)頁上實現(xiàn)復(fù)雜的音頻處理、實時通信成為可能。

WebAPI的工作流程

  1. 「調(diào)用API」:開發(fā)者在JavaScript代碼中調(diào)用某個WebAPI。
  2. 「瀏覽器解釋執(zhí)行」: 瀏覽器解釋JavaScript代碼,并「執(zhí)行相應(yīng)的API調(diào)用」。
  3. 「API內(nèi)部處理」:WebAPI內(nèi)部可能會執(zhí)行多種操作,如觸發(fā)網(wǎng)絡(luò)請求、訪問數(shù)據(jù)庫、啟動硬件設(shè)備等。
  4. 「回調(diào)和事件循環(huán)」:對于異步操作,WebAPI通常會使用回調(diào)函數(shù)或Promise來處理操作完成后的結(jié)果。瀏覽器的事件循環(huán)機制確保了這些回調(diào)在適當?shù)臅r候被調(diào)用。
  5. 「渲染和更新」:對于涉及視覺變化的API,如DOM操作或Canvas繪圖,瀏覽器會更新頁面內(nèi)容,這通常發(fā)生在瀏覽器的下一個重繪周期。

在整個過程中,「瀏覽器的角色是中介」,它提供了執(zhí)行API的環(huán)境和必要的安全措施。這些API讓W(xué)eb應(yīng)用可以像本地應(yīng)用一樣豐富和強大,同時仍然運行在瀏覽器這個相對安全的沙箱環(huán)境中。

下面的圖,展示了WebAPI的地位(中間部分)。

圖片圖片

GPU硬件加速

「GPU(Graphics Processing Unit)硬件加速」是一種利用GPU來執(zhí)行圖形和計算任務(wù)的技術(shù)。在Web開發(fā)中,GPU硬件加速可以通過利用用戶計算機中的GPU資源來加速瀏覽器的渲染和繪制操作。這通??梢蕴岣呔W(wǎng)頁的性能和流暢度,尤其是對于需要大量圖形操作的頁面。

在Web開發(fā)中,一些CSS屬性和操作可以觸發(fā)GPU硬件加速,以便更有效地利用GPU資源。

  1. 3D 變換(transform)

使用transform屬性進行3D變換,如translate3d、rotate3d、scale3d等,可以觸發(fā)GPU硬件加速。例如:

.element {
  transform: translate3d(0, 0, 0);
}
  1. CSS 動畫(animation)和過渡(transition):
  • 使用CSS動畫和過渡屬性,例如transform屬性的過渡,可以觸發(fā)GPU硬件加速。例如:

    .element {
      transition: transform 0.3s ease-in-out;
    }

  1. Canvas 繪圖:

  • 在<canvas>元素上進行繪圖操作通常會利用GPU硬件加速。這包括使用2D或WebGL上下文進行圖形渲染。

  1. 使用 will-change 屬性:

  • will-change屬性告訴瀏覽器某個屬性將會被改變,從而可以提前進行優(yōu)化。例如:

    .element {
      will-change: transform;
    }

  1. 使用 image-rendering 屬性:

  • image-rendering屬性用于指定圖像的渲染質(zhì)量,而且在某些情況下也能觸發(fā)GPU硬件加速。例如:

    .element {
      image-rendering: pixelated;
    }

  1. 使用 backface-visibility 屬性:

  • backface-visibility屬性用于指定當元素不面向屏幕時是否可見。在某些情況下,該屬性的使用可以觸發(fā)GPU硬件加速。例如:

    .element {
      backface-visibility: hidden;
    }

  1. 使用 filter 屬性(某些情況下):

  • 在某些情況下,使用filter屬性(如模糊、對比度等)可能觸發(fā)GPU硬件加速。

還記得我們在你會在瀏覽器中打斷點嗎?我會!中介紹過如何看chromium 在線倉庫[2]

那我們就從源碼的角度來看看,為什么上面的屬性會走GPU硬件加速

或者我們可以看compositing_reason_finder.cc這個文件,它例舉了很多枚舉類型。

圖片圖片

2. 事件循環(huán)(Event Loop)

事件循環(huán)就是一個「死循環(huán)」,不死不休。

舊的操作系統(tǒng)不支持多線程,它們的事件循環(huán)可以被大致描述為一個簡單的循環(huán):

while (true) {
    if (hasTasks()) {
        executeTask();
    }
}

現(xiàn)代操作系統(tǒng)的調(diào)度器(schedulers)非常復(fù)雜。它們有優(yōu)先級設(shè)置、執(zhí)行隊列等許多其他技術(shù)。

這里做一個題外話,看到schedulers/優(yōu)先級設(shè)置是不是想到React-Fiber架構(gòu)了。其實,React在內(nèi)部就是模仿操作系統(tǒng),做了自己的實現(xiàn)邏輯。(這里就不展開說明了)

為了讓事情簡單化,我們可以將事件循環(huán)(Event Loop)描述為一個循環(huán),該循環(huán)檢查是否有任何待處理的任務(wù):

圖片圖片


任務(wù)觸發(fā)器

瀏覽器屬于「事件驅(qū)動」的技術(shù)框架,如果想讓Event Loop探查并執(zhí)行對應(yīng)的任務(wù),首先要做的就是將某些任務(wù)進行觸發(fā)。也就是喚起指定任務(wù)的觸發(fā)器。

下面就是我們平時能夠接觸到的任務(wù)觸發(fā)器

  1. 「<script>標簽」:通過HTML的<script>標簽引入的代碼會被瀏覽器解析并執(zhí)行,相關(guān)的同步任務(wù)會被放入事件循環(huán)中。
  2. 「延后的任務(wù)」:

setTimeout:設(shè)置一個計時器,在指定的延時后執(zhí)行一段代碼。

setInterval:設(shè)置一個計時器,按照指定的時間間隔重復(fù)執(zhí)行一段代碼。

requestIdleCallback:安排一個函數(shù)在瀏覽器空閑時期被調(diào)用。

  1. 「瀏覽器API的事件處理程序」:

用戶觸發(fā)的事件,例如click, mousedown, input, blur等。

代碼生成的事件,比如XMLHttpRequest的響應(yīng)處理、fetch API的promise resolve等。

  1. 「Promise狀態(tài)變化」:當一個Promise對象的狀態(tài)改變時(例如從pending變?yōu)閒ulfilled或rejected),相關(guān)的任務(wù)會被加入事件循環(huán)。

  2. 「觀察者」:

DOMMutationObserver:用于觀察DOM變動,當DOM發(fā)生變化時可以通知應(yīng)用。

IntersectionObserver:用于觀察元素是否進入了父元素或視口的特定區(qū)域。

  1. requestAnimationFrame:用于在「下一次重新渲染前」執(zhí)行動畫或視覺更新的函數(shù),使動畫流暢。

上面的任務(wù)幾乎都是通過WebAPI進行觸發(fā)的。

例如,我們在代碼中有這樣一行:setTimeout(function a() {}, 100)。當我們執(zhí)行setTimeout時,WebAPI將任務(wù)延遲了100毫秒。100毫秒后,WebAPI將函數(shù)a()放入任務(wù)隊列(Task Queue)(也可以稱為Callback Queue)中。事件循環(huán)在下一個循環(huán)中獲取該任務(wù)并執(zhí)行它。

JS執(zhí)行和頁面渲染是難兄難弟

EventLoop = TaskQueue + RenderQueue

在之前的文章中,我們提到過文檔對象模型(DOM)是一個應(yīng)用編程接口(API),通過創(chuàng)建表示文檔的樹,以一種「獨立于平臺和語言」的方式訪問和修改一個頁面的內(nèi)容和結(jié)構(gòu)。

在HTML文檔中,Web開發(fā)者可以使用JS來CRUD DOM 結(jié)構(gòu),其主要的目的是「動態(tài)」改變HTML文檔的結(jié)構(gòu)。

  • 讀取DOM元素的數(shù)據(jù):大小、屬性、位置等
  • 改變屬性:data-屬性、寬度、高度、位置、CSS屬性等
  • 創(chuàng)建/刪除HTML節(jié)點

而在JS把玩DOM之后,就將其扔給了瀏覽器的渲染引擎,而渲染引擎任勞任怨的處理DOM和DOM攜帶的附帶信息,并將其渲染成用戶心儀的頁面。

也就是說「JS和瀏覽器(渲染引擎)都能染指過DOM」。

基于上面的特定的背景,我們可以得出一個結(jié)論,執(zhí)行JS和渲染頁面都在同一個線程里。

這意味著事件循環(huán)包含渲染流程。而渲染流程不是單一的操作。其實,它是渲染隊列:

圖片圖片

現(xiàn)在EventLoop處理鏈路上有兩個任務(wù)源。

  1. JS任務(wù) - SomeJsTasks
  2. 渲染任務(wù) - RenderQueue

屏幕更新

對于瀏覽器來說,事件循環(huán)與幀(frames)密切相關(guān),因為EventLoop同時執(zhí)行JS代碼并渲染頁面。

可以將幀視為屏幕狀態(tài)的快照,即用戶在某一時刻看到的畫面。

圖片圖片

我們在Chromium 最新渲染引擎--RenderingNG也介紹過frames。

圖片圖片

瀏覽器的目標是盡快顯示頁面更新,考慮到硬件和軟件的限制:

  • 硬件限制:屏幕刷新率
  • 軟件限制:操作系統(tǒng)設(shè)置、瀏覽器及其設(shè)置、節(jié)能設(shè)置等

絕大多數(shù)瀏覽器/操作系統(tǒng)支持60幀每秒(Frames Per Second,F(xiàn)PS)。瀏覽器試圖以這個特定的速率更新屏幕。

當我們使用60 FPS時,這意味著瀏覽器在必須「渲染新幀之前有16.6毫秒的時間段來執(zhí)行任務(wù)」(1000/60),而渲染新幀也會消耗時間。

3. 任務(wù)隊列/微任務(wù)隊列/調(diào)用棧

瀏覽器使用兩個隊列來執(zhí)行我們的JS代碼:

  • 任務(wù)隊列(TaskQueue)或宏任務(wù)隊列專用于所有事件、延遲任務(wù)等。
  • 微任務(wù)隊列(Microtask Queue)用于處理 promise 回調(diào)(已解決和已拒絕),以及 MutationObserver。這個隊列中的單個元素被稱為 微任務(wù)。

任務(wù)隊列(Task Queue)

當瀏覽器收到一個新任務(wù)時,它將任務(wù)放入任務(wù)隊列。每個事件循環(huán)定期從任務(wù)隊列中獲取任務(wù)并執(zhí)行它。任務(wù)完成后,「如果瀏覽器有時間(渲染隊列沒有任務(wù)),事件循環(huán)從任務(wù)隊列獲取另一個任務(wù),直到渲染隊列接收到任務(wù)為止」。

案例分析1

圖片圖片

我們有3個任務(wù):A、B、C。事件循環(huán)獲取并執(zhí)行第一個任務(wù),花費了4毫秒。然后事件循環(huán)檢查其他隊列(微任務(wù)隊列和渲染隊列),它們是空的。事件循環(huán)執(zhí)行任務(wù)B,花費了12毫秒??偣矁蓚€任務(wù)使用了16毫秒。然后瀏覽器將任務(wù)添加到渲染隊列以繪制新幀。事件循環(huán)檢查渲染隊列并開始執(zhí)行渲染隊列中的任務(wù),它們大約花費1毫秒。完成這些操作后,事件循環(huán)返回到任務(wù)隊列并執(zhí)行最后一個任務(wù)C。

事件循環(huán)無法預(yù)測任務(wù)將花費多少時間。此外,事件循環(huán)「無法暫停任務(wù)來渲染幀」,因為瀏覽器引擎不知道該任務(wù)是否會對繪制內(nèi)容有修改動作,還是任務(wù)只是為了渲染幀做了一些無關(guān)痛癢的準備工作。

在執(zhí)行JS代碼期間,「JS所做的所有更改并不會直接呈現(xiàn)給用戶,而是等到宏任務(wù)和所有待處理的微任務(wù)完成后才會表現(xiàn)出來」。但是,此時在JS中可以獲取到最新DOM的變更信息。

案例分析2

圖片圖片

隊列中只有2個任務(wù)(A、B)。第一個任務(wù)A花費了240毫秒(無法中斷)。由于60FPS意味著每16.6毫秒應(yīng)該渲染一幀,所以瀏覽器有14幀的空窗期。當任務(wù)A結(jié)束時,事件循環(huán)執(zhí)行渲染隊列中的任務(wù)以繪制新幀。

「盡管我們失去了14幀,這并不意味著我們將連續(xù)渲染15幀。它只會渲染最后一幀」。

這就是當JS中有長任務(wù)執(zhí)行時,會阻塞頁面的渲染,如果這14幀中間操作了過多的DOM,頁面中就會有一種從第一幀到第十五幀的跳動。這就是為什么我們總是要將長任務(wù)拆分成很多小任務(wù)的原因。

調(diào)用棧(Call Stack)

在Event Loop 可視化解析講解中我們對調(diào)用棧有過介紹。

圖片圖片

案例分析

function D() {
  debugger;
  console.log('前端柒八九');
}

function C() {
  D();
}

function B() {
  C();  
}

function other() {
   // 不在我們考察堆棧上下文中
}

function A() {
  const arr = [];
  while (arr.length < 2) {
    arr.push(other());
  }
  B();
}
console.log(A());

將上面的代碼貼到devTool-Console或者devTool-Source-Snippet中執(zhí)行。這段代碼將在debugger處暫停。

這里多提一嘴,關(guān)于如何在瀏覽器中優(yōu)雅的調(diào)試斷點,可以參考你會在瀏覽器中打斷點嗎?我會!

  • console.log(A());,它是調(diào)用棧的開始。
  • 然后我們進入 A 函數(shù),并多次調(diào)用 other。在我們到達debugger之前,這個函數(shù)不會出現(xiàn)在調(diào)用棧中,因為它在我們到達調(diào)試器之前就結(jié)束了。

這是我們在debugger停止時調(diào)用棧的樣子:

圖片圖片

圖中(anonymous)表示全局作用域,我們沒貼出來,它就是指向25行調(diào)用棧的入口的。

當調(diào)用棧為空時,「當前任務(wù)」完成。

微任務(wù)

微任務(wù)只有兩個可能的來源:

  1. Promise 回調(diào)(onResolved/onRejected)
  2. MutationObserver 回調(diào)。

微任務(wù)有一個主要特征,使它們與其他任務(wù)完全不同:

一旦調(diào)用棧為空,微任務(wù)將立即執(zhí)行。

微任務(wù)可以創(chuàng)建其他微任務(wù),「這些微任務(wù)將在調(diào)用棧結(jié)束時執(zhí)行」。每個「新的微任務(wù)都會推遲執(zhí)行新的宏任務(wù)或新幀的渲染」。

案例分析

在這個例子中,微任務(wù)隊列中有4個微任務(wù):

圖片圖片

要執(zhí)行的第一個微任務(wù)是A。A花費了200毫秒,而且我們在渲染隊列中有任務(wù)。然而,它們將被推遲,因為我們?nèi)匀挥?個微任務(wù)。這意味著在執(zhí)行A后,事件循環(huán)將執(zhí)行微任務(wù)B、C,最后是D。當「微任務(wù)隊列變空時,事件循環(huán)渲染新幀」。在這個例子中,這4個微任務(wù)花費了0.5秒。在這段時間內(nèi),瀏覽器「UI被阻塞,不可交互」。

后續(xù)的微任務(wù)可以阻塞網(wǎng)站UI,使頁面變得不可交互。

這個微任務(wù)特性既可能是優(yōu)勢也可能是劣勢。例如,當 MutationObserver 根據(jù)DOM更改調(diào)用其回調(diào)時,用戶在回調(diào)完成之前看不到頁面上的更改。因此,我們可以有效地管理用戶看到的內(nèi)容。

更新后的事件循環(huán)圖示:

圖片圖片

各自的特性

  • 調(diào)用棧是用于跟蹤「正在被執(zhí)行」函數(shù)的機制,而宏任務(wù)隊列是用于跟蹤「將要被執(zhí)行」函數(shù)的機制。
  • 宏任務(wù)隊列和微任務(wù)隊列都是「FIFO」(先進先出)的隊列結(jié)構(gòu),這些任務(wù)是「同步阻塞」的

4. 在渲染隊列中執(zhí)行的是什么?

其實,在瀏覽器中渲染頁面是有很多步驟的。

同時還涉及多個進程之間的通信。這在之前的頁面是如何生成的(宏觀角度)有過介紹,這里就不在羅嗦了。

而今天呢,我們從瀏覽器渲染幀的角度來看到底發(fā)生了啥?!其實幀渲染不是一個單一的操作,它有幾個階段,每個階段都可以分為子階段。

新幀渲染的基本結(jié)構(gòu)

RequestAnimationFrame(RAF)

圖片圖片

requestAnimationFrame 是一個由瀏覽器提供的 JavaScript API,用于在下一次「瀏覽器重繪之前」執(zhí)行指定的函數(shù)。這個函數(shù)通常用于執(zhí)行動畫或其他需要高性能更新的任務(wù),因為它會在瀏覽器的繪制周期內(nèi)運行,以確保動畫的平滑流暢。

特點

  1. 「與瀏覽器的重繪同步:」 requestAnimationFrame 的執(zhí)行時機與瀏覽器的重繪周期相同,通常是每秒60次(60幀每秒),這確保了動畫的流暢性。
  2. 「自動暫停:」 當用戶切換到其他標簽頁或最小化瀏覽器時,requestAnimationFrame 會自動暫停,從而節(jié)省系統(tǒng)資源。
  3. 「避免卡頓:」 由于與瀏覽器的繪制同步,requestAnimationFrame 可以避免由于連續(xù)執(zhí)行任務(wù)導(dǎo)致的卡頓和性能問題。
  4. RAF的回調(diào)有一個DOMHighResTimeStamp參數(shù),它是自時間起源[3]以來經(jīng)過的毫秒數(shù),即文檔生命周期的開始。我們不需要在回調(diào)中使用performance.now();
  5. RAF返回一個描述符(id),因此你可以使用cancelAnimationFrame取消RAF回調(diào)(就像使用setTimeout一樣);
  6. 更改元素大小或讀取元素屬性的JS代碼會強制使用requestAnimationFrame;

樣式重新計算(Recalc Style)

圖片圖片

瀏覽器重新計算應(yīng)用的樣式。此步驟還會計算哪些媒體查詢將處于活動狀態(tài)。

以下操作能觸發(fā)重新計算包括

  • 直接更改,比如 a.styles.left = '10px'
  • 通過CSS文件描述的更改,比如 element.classList.add('my-styles-class')。

此過程可能觸發(fā)整個DOM樹的整體計算也可以是局部小范圍的計算過程,取決于「被改動的元素的位置」。

  • 例如,改動body元素的屬性,就會發(fā)生整個DOM樹的重新計算。

圖片圖片

將元素樣式和DOM元素結(jié)合起來,就會生成Render Tree

我們可以通過devTool-Performance來檢測網(wǎng)站在這步所花費的時間。

圖片圖片

React官網(wǎng)-樣式重新計算所花費時間

布局(Layout)

計算每個「可視元素」的位置信息(距離視口的距離和元素本身大小)。并生成對應(yīng)的Layout Tree。 頁面上的DOM元素越多,操作就越復(fù)雜。

圖片圖片

React官網(wǎng)-布局所花費時間React官網(wǎng)-布局所花費時間

觸發(fā)條件

對于現(xiàn)今「富應(yīng)用」來講,做頁面中做元素的移動和變更那是家常便飯。以下操作就會觸發(fā)對應(yīng)的布局流程

  • 讀取與元素的大小和位置相關(guān)的屬性(offsetWidth、offsetLeft、getBoundingClientRect等)。
  • 寫入與元素的大小和位置相關(guān)的屬性,除了某些屬性(例如 transform 和 will-change)。

這里,我們用一點篇幅來講一下為何transform/will-change能跳過布局階段,直接進入合成階段。

在之前的Chromium 最新渲染引擎--RenderingNG我們講過,在渲染流程的最開始其實是還有一個步驟叫做animate。

圖片圖片

在渲染流程的圖中,用不同顏色來標識該階段可能會被不同的線程或者進程所執(zhí)行。

顏色

所在進程/線程

綠色

渲染進程中的主線程

黃色

渲染進程中的合成線程

橘色

viz進程(也叫GPU進程)

在某些階段,可能會被多個地方所執(zhí)行,所以該階段可能存在多個顏色。

而animate存在兩個顏色(綠色和黃色)也就是這一步,會在多個地方被執(zhí)行。

而讓animate能夠在黃色階段,也就是在合成線程中執(zhí)行,就是我們使用了一些CSS3屬性

  1. transform
  2. opacity
  3. filter
  4. will-change

使用了這些屬性后,會跳過前面很多的步驟,例如重新計算樣式/布局/重繪等階段。并且,在合成線程進行數(shù)據(jù)轉(zhuǎn)換后,開啟GPU的「硬件加速」。

就這速度,你說能不快嗎。

其實,上面的內(nèi)容在大部分教程中,都是一種鐵打不動的定律。使用了transform/will-change開啟了GPU硬件加速,所以性能提升了。

強制布局

強制布局(Forced Synchronous Layout 或 Forced Reflow)是Web性能優(yōu)化領(lǐng)域的一個術(shù)語,它指的是瀏覽器在能夠繼續(xù)「處理后續(xù)操作之前,必須完成當前的布局計算」。

當強制執(zhí)行布局時,瀏覽器會暫停JS主線程,盡管調(diào)用棧不是空的。

有很多我們耳熟能詳?shù)牟僮鳎紩|發(fā)強制布局。

圖片圖片

想了解更多??觸發(fā)強制布局的操作[4]。

案例分析
div1.style.height = "200px"; // 更改元素大小
var height1 = div1.clientHeight; // 讀取屬性

瀏覽器無法在不重新計算其實際大小的情況下計算div1的clientHeight。在這種情況下,瀏覽器暫停JS執(zhí)行并運行:樣式以查看應(yīng)該更改什么,以及布局以重新計算大小。布局不僅計算放置在div1之前的元素,還計算放置在div1之后的元素。現(xiàn)代瀏覽器通過優(yōu)化計算,使得在每次都不必重新計算整個DOM樹,但在糟糕的情況下仍然會發(fā)生。重新計算的過程稱為「layout shift」。

圖片圖片

瀏覽器盡量不在每次都強制執(zhí)行布局。因此,它們對操作進行分組:

div1.style.height = "200px";
var height1 = div1.clientHeight; // <-- 布局 1
div2.style.margin = "300px";
var height2 = div2.clientHeight; // <-- 布局 2
  • 在第一行上,瀏覽器計劃了高度的更改。
  • 在第二行上,瀏覽器收到了讀取屬性的請求。由于我們有掛起的高度更改,瀏覽器必須強制執(zhí)行布局。
  • 在第三 + 四行上我們有相同的情況。為了讓瀏覽器更好地處理,我們可以組合讀取和寫入操作:
div1.style.height = "200px";
div2.style.margin = "300px";
var height1 = div1.clientHeight; // <-- 布局 1
var height2 = div2.clientHeight;

通過組合元素,我們擺脫了第二次布局,因為當瀏覽器達到第四行時,它已經(jīng)有了所有的數(shù)據(jù)。

我們的事件循環(huán)從一個循環(huán)變成了多個,因為我們可以在任務(wù)和微任務(wù)階段都強制執(zhí)行布局:

圖片圖片

優(yōu)化布局

  • 將讀取/寫入操作分組,擺脫不必要的布局
  • 優(yōu)先采用硬件加速
  • 批量DOM更改:集中進行DOM更改,然后一次性讀取布局信息,可以減少強制布局的次數(shù)。
  • 避免布局抖動:布局抖動是指在一系列操作中交替進行讀寫操作,導(dǎo)致多次布局計算。通過避免布局抖動,可以改善性能。
  • 使用現(xiàn)代CSS布局技術(shù):如Flexbox和Grid,這些技術(shù)可以提高布局性能,尤其是在動態(tài)內(nèi)容變化時。
  • 使用虛擬化:如在長列表中,只渲染進入視口的元素,可以極大地減少布局計算的負擔(dān)。

繪制(Paint)

圖片圖片

在Layout階段已經(jīng)把可見元素都安排的明明白白了,現(xiàn)在我們就需要對其粉飾一番了。也就是對其濃墨重彩的處理下。

圖片圖片

這個操作通常不會消耗很多時間,可能在第一次渲染時可能會很大。

合成(Composition)

圖片圖片

合成是默認在GPU上運行的唯一階段。

圖片圖片

在這一步中,瀏覽器僅執(zhí)行特定的CSS樣式,比如transform。(硬件加速那塊介紹過了)

重要說明:transform: translate 不會在GPU上啟動渲染。因此,如果我們在代碼中有 transform: translateZ(0) 來將渲染移到GPU上,這樣是行不通的。這是一個誤解。

我們可以通過源碼找到原因

圖片圖片

在devTool可以在GPU的信息中看到所消耗的時間

圖片圖片

5. EventLoop模型

通過上面的對各個節(jié)點的分析,最終EventLoop的模型圖如下所示:

圖片圖片

偽代碼表示EventLoop

// 開始一個無限循環(huán),模擬瀏覽器的事件循環(huán)
while (true) {
    // 記錄任務(wù)開始的時間
    const taskStartTime = performance.now();

    // 從事件隊列中取出一個任務(wù)
    const task = eventQueue.pop();
    // 如果任務(wù)存在,則運行該任務(wù)
    if (task)
        task.run();
    // 如果任務(wù)運行時間超過50ms,則報告長任務(wù)
    if (performance.now() - taskStartTime > 50)
        reportLongTask();

    // 如果沒有渲染機會(可能是因為當前處于忙碌狀態(tài)或頻率限制),則繼續(xù)下一次循環(huán)
    if (!hasRenderingOpportunity())
        continue;

    // 調(diào)用所有排隊的動畫幀回調(diào)
    invokeAnimationFrameCallbacks();

    // 如果需要樣式計算和布局,則執(zhí)行它們
    while (needsStyleAndLayout()) {
        // 執(zhí)行樣式計算和布局
        styleAndLayout();
        // 調(diào)用所有的尺寸調(diào)整觀察者回調(diào)
        invokeResizeObservers();
    }

    // 標記繪制時間,這可能是用于性能監(jiān)控
    markPaintTiming();
    // 執(zhí)行渲染,更新屏幕上的內(nèi)容
    render();
}

長任務(wù) 和 50ms

上面的偽代碼中,我們有兩個針對于本文來講比較陌生的詞或者變量

  • 長任務(wù)
  • 50ms

其實,我們之前在介紹瀏覽器性能指標時有過接觸的。下面我就把這些羅列一下,不做過多的解釋了。

我們在瀏覽器之性能指標-TBT中介紹過

任何超過50毫秒的任務(wù)被認為是「長任務(wù)」

在瀏覽器之性能指標-TTI中的標準中也提到過50ms

圖片圖片

在瀏覽器之性能指標-INP中,我們進行INP優(yōu)化時也涉及長任務(wù)的優(yōu)化處理 圖片 圖片

責(zé)任編輯:武曉燕 來源: 前端柒八九
相關(guān)推薦

2024-01-08 20:05:32

2024-03-27 13:33:00

MySQLInnoDB事務(wù)

2024-05-30 08:19:52

微服務(wù)架構(gòu)大型應(yīng)用

2023-06-09 07:18:03

開源數(shù)據(jù)庫

2023-05-11 08:14:58

國產(chǎn)數(shù)據(jù)庫用戶

2024-01-25 09:10:10

GoRust標準庫

2023-12-26 07:37:27

2023-06-14 17:56:54

2022-12-30 08:35:00

2023-04-26 00:00:00

框架Vue.js客戶

2023-06-08 09:55:03

冪等計算機系統(tǒng)

2022-10-10 18:38:56

inert屬性鍵盤

2022-04-07 11:15:22

PulseEventAPI函數(shù)

2023-12-28 08:43:28

前端算法搜索

2022-10-19 08:19:32

動態(tài)基線預(yù)警

2023-06-14 08:15:34

算法合并操作Winner

2023-12-06 08:01:03

CSSPostCSS

2022-10-24 20:25:40

云原生SpringJava

2022-10-08 08:09:13

MGRGreatSQL事務(wù)

2023-01-02 23:58:03

點贊
收藏

51CTO技術(shù)棧公眾號