成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

開發 前端
在整個過程中,「瀏覽器的角色是中介」,它提供了執行API的環境和必要的安全措施。這些API讓Web應用可以像本地應用一樣豐富和強大,同時仍然運行在瀏覽器這個相對安全的沙箱環境中。

前言

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

Event Loop圖片

上文我們從偏JS調用機制的角度分析了,調用棧(Call Stack)/宏任務隊列 (Task Queue)和微任務隊列 (Microtask Queue)他們之間的關系和他們是如何協同合作的。并且,舉了很多例子,用可視化的方式講解它們如何工作的。

而今天,我們從瀏覽器內部的實現細節來談談EventLoop是如何從接受任務到渲染出對應頁面的。

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

圖片圖片

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

我們能所學到的知識點

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

1. 前置知識點

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

頁面刷新術語

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

  • 「屏幕刷新頻率」

一秒內屏幕刷新的次數(一秒內顯示了多少幀的圖像),單位 Hz(赫茲),如常見的 60 Hz。「刷新頻率取決于硬件的固定參數」(不會變的)。

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

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

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

  • 「幀率 (Frame Rate)」

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

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

  • 「畫面撕裂(tearing)」

  • 一個屏幕內的數據來自2個不同的幀,畫面會出現撕裂感。

測試幀率

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

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

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

下面是,我們在React-官網[1]中實驗的結果。

圖片圖片

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

WebAPI

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

APIs

描述

網絡請求

(Network Requests)

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


DOM操作

(DOM Manipulation)

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

事件處理

(Event Handling)

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

存儲機制

(Storage Mechanisms)

瀏覽器提供了如localStoragesessionStorageIndexedDB等API,可以在用戶的設備上存儲數據。

設備API

(Device APIs)

可以訪問設備的硬件,如攝像頭、麥克風、地理位置等,這通常通過navigator對象暴露的API實現。

圖形和動畫

(Graphics & Animation)

CanvasWebGL API允許在網頁上繪制二維和三維圖形。requestAnimationFrame為動畫提供了一個優化的循環機制。

性能監控

(Performance Monitoring)

Performance API提供了獲取瀏覽器性能相關數據的接口,幫助開發者監控和優化網頁性能。

其他API

(Other APIs)

還有諸如Web Audio APIWebRTCWebSocket等,使得在網頁上實現復雜的音頻處理、實時通信成為可能。

WebAPI的工作流程

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

在整個過程中,「瀏覽器的角色是中介」,它提供了執行API的環境和必要的安全措施。這些API讓Web應用可以像本地應用一樣豐富和強大,同時仍然運行在瀏覽器這個相對安全的沙箱環境中。

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

圖片圖片

GPU硬件加速

「GPU(Graphics Processing Unit)硬件加速」是一種利用GPU來執行圖形和計算任務的技術。在Web開發中,GPU硬件加速可以通過利用用戶計算機中的GPU資源來加速瀏覽器的渲染和繪制操作。這通常可以提高網頁的性能和流暢度,尤其是對于需要大量圖形操作的頁面。

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

  1. 3D 變換(transform)

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

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

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

  1. Canvas 繪圖:

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

  1. 使用 will-change 屬性:

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

    .element {
      will-change: transform;
    }

  1. 使用 image-rendering 屬性:

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

    .element {
      image-rendering: pixelated;
    }

  1. 使用 backface-visibility 屬性:

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

    .element {
      backface-visibility: hidden;
    }

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

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

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

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

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

圖片圖片

2. 事件循環(Event Loop)

事件循環就是一個「死循環」,不死不休。

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

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

現代操作系統的調度器(schedulers)非常復雜。它們有優先級設置、執行隊列等許多其他技術。

這里做一個題外話,看到schedulers/優先級設置是不是想到React-Fiber架構了。其實,React在內部就是模仿操作系統,做了自己的實現邏輯。(這里就不展開說明了)

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

圖片圖片


任務觸發器

瀏覽器屬于「事件驅動」的技術框架,如果想讓Event Loop探查并執行對應的任務,首先要做的就是將某些任務進行觸發。也就是喚起指定任務的觸發器。

下面就是我們平時能夠接觸到的任務觸發器

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

setTimeout:設置一個計時器,在指定的延時后執行一段代碼。

setInterval:設置一個計時器,按照指定的時間間隔重復執行一段代碼。

requestIdleCallback:安排一個函數在瀏覽器空閑時期被調用。

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

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

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

  1. 「Promise狀態變化」:當一個Promise對象的狀態改變時(例如從pending變為fulfilled或rejected),相關的任務會被加入事件循環。

  2. 「觀察者」:

DOMMutationObserver:用于觀察DOM變動,當DOM發生變化時可以通知應用。

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

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

上面的任務幾乎都是通過WebAPI進行觸發的。

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

JS執行和頁面渲染是難兄難弟

EventLoop = TaskQueue + RenderQueue

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

在HTML文檔中,Web開發者可以使用JS來CRUD DOM 結構,其主要的目的是「動態」改變HTML文檔的結構。

  • 讀取DOM元素的數據:大小、屬性、位置等
  • 改變屬性:data-屬性、寬度、高度、位置、CSS屬性等
  • 創建/刪除HTML節點

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

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

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

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

圖片圖片

現在EventLoop處理鏈路上有兩個任務源。

  1. JS任務 - SomeJsTasks
  2. 渲染任務 - RenderQueue

屏幕更新

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

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

圖片圖片

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

圖片圖片

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

  • 硬件限制:屏幕刷新率
  • 軟件限制:操作系統設置、瀏覽器及其設置、節能設置等

絕大多數瀏覽器/操作系統支持60幀每秒(Frames Per Second,FPS)。瀏覽器試圖以這個特定的速率更新屏幕。

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

3. 任務隊列/微任務隊列/調用棧

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

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

任務隊列(Task Queue)

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

案例分析1

圖片圖片

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

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

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

案例分析2

圖片圖片

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

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

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

調用棧(Call Stack)

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

圖片圖片

案例分析

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中執行。這段代碼將在debugger處暫停。

這里多提一嘴,關于如何在瀏覽器中優雅的調試斷點,可以參考你會在瀏覽器中打斷點嗎?我會!

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

這是我們在debugger停止時調用棧的樣子:

圖片圖片

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

當調用棧為空時,「當前任務」完成。

微任務

微任務只有兩個可能的來源:

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

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

一旦調用棧為空,微任務將立即執行。

微任務可以創建其他微任務,「這些微任務將在調用棧結束時執行」。每個「新的微任務都會推遲執行新的宏任務或新幀的渲染」。

案例分析

在這個例子中,微任務隊列中有4個微任務:

圖片圖片

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

后續的微任務可以阻塞網站UI,使頁面變得不可交互。

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

更新后的事件循環圖示:

圖片圖片

各自的特性

  • 調用棧是用于跟蹤「正在被執行」函數的機制,而宏任務隊列是用于跟蹤「將要被執行」函數的機制。
  • 宏任務隊列和微任務隊列都是「FIFO」(先進先出)的隊列結構,這些任務是「同步阻塞」的

4. 在渲染隊列中執行的是什么?

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

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

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

新幀渲染的基本結構

RequestAnimationFrame(RAF)

圖片圖片

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

特點

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

樣式重新計算(Recalc Style)

圖片圖片

瀏覽器重新計算應用的樣式。此步驟還會計算哪些媒體查詢將處于活動狀態。

以下操作能觸發重新計算包括

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

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

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

圖片圖片

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

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

圖片圖片

React官網-樣式重新計算所花費時間

布局(Layout)

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

圖片圖片

React官網-布局所花費時間React官網-布局所花費時間

觸發條件

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

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

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

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

圖片圖片

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

顏色

所在進程/線程

綠色

渲染進程中的主線程

黃色

渲染進程中的合成線程

橘色

viz進程(也叫GPU進程)

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

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

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

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

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

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

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

強制布局

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

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

有很多我們耳熟能詳的操作,都會觸發強制布局。

圖片圖片

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

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

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

圖片圖片

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

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

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

我們的事件循環從一個循環變成了多個,因為我們可以在任務和微任務階段都強制執行布局:

圖片圖片

優化布局

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

繪制(Paint)

圖片圖片

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

圖片圖片

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

合成(Composition)

圖片圖片

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

圖片圖片

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

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

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

圖片圖片

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

圖片圖片

5. EventLoop模型

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

圖片圖片

偽代碼表示EventLoop

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

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

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

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

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

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

長任務 和 50ms

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

  • 長任務
  • 50ms

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

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

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

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

圖片圖片

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

責任編輯:武曉燕 來源: 前端柒八九
相關推薦

2024-01-08 20:05:32

2024-03-27 13:33:00

MySQLInnoDB事務

2023-06-09 07:18:03

開源數據庫

2024-05-30 08:19:52

微服務架構大型應用

2024-01-25 09:10:10

GoRust標準庫

2023-05-11 08:14:58

國產數據庫用戶

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客戶

2022-10-10 18:38:56

inert屬性鍵盤

2023-06-08 09:55:03

冪等計算機系統

2022-04-07 11:15:22

PulseEventAPI函數

2023-12-28 08:43:28

前端算法搜索

2022-10-19 08:19:32

動態基線預警

2023-12-06 08:01:03

CSSPostCSS

2022-10-24 20:25:40

云原生SpringJava

2023-06-14 08:15:34

算法合并操作Winner

2015-09-18 09:17:06

數據分析

2023-01-02 23:58:03

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久香蕉网 | 日本免费在线观看视频 | 奇米久久 | 久久99国产精品久久99果冻传媒 | 久久久久av| 欧美福利 | 国产情侣激情 | 一区二区不卡视频 | 国产91久久久久久久免费 | 尤物在线精品视频 | 国产精品免费观看 | 成人亚洲精品久久久久软件 | 日韩成人精品一区二区三区 | 日本黄色免费片 | 日日噜噜噜夜夜爽爽狠狠视频, | 国产1区2区在线观看 | 欧美日韩电影一区 | 久久精品欧美一区二区三区麻豆 | 成人二区 | 请别相信他免费喜剧电影在线观看 | 国产综合在线视频 | 欧美日韩国产精品一区 | 青青草av在线播放 | 国产免费观看一级国产 | caoporon| 一区二区三区四区视频 | 久久久精品一区 | 欧美日韩国产一区二区三区 | 国产精品日韩欧美一区二区三区 | 日韩中出 | 香蕉婷婷 | 风间由美一区二区三区在线观看 | 国产玖玖 | 亚洲欧美日韩电影 | 国产精品国产三级国产aⅴ原创 | 91免费版在线观看 | 先锋影音资源网站 | 久久精品二区亚洲w码 | 国产91精品网站 | 黄色一级电影在线观看 | 久久久久久免费毛片精品 |