天天看點

怎麼設定div為資料渲染後的高度_前端面試大全:浏覽器渲染原理浏覽器接收到 HTML 檔案并轉換為 DOM 樹将 CSS 檔案轉換為 CSSOM 樹生成渲染樹為什麼操作 DOM 慢什麼情況阻塞渲染重繪(Repaint)和回流(Reflow)思考題小結

在這一篇文章中,我們将一起學習浏覽器渲染原理這部分的知識。你可能會有疑問,我又不是做浏覽器研發的,為什麼要來學習這個?其實我們學習浏覽器渲染原理更多的是為了解決性能的問題,如果你不了解這部分的知識,你就不知道什麼情況下會對性能造成損傷。并且渲染原理在面試中答得好,也是一個能與其他候選人拉開差距的一點。

我們知道執行 JS 有一個 JS 引擎,那麼執行渲染也有一個渲染引擎。同樣,渲染引擎在不同的浏覽器中也不是都相同的。比如在 Firefox 中叫做 Gecko,在 Chrome 和 Safari 中都是基于 WebKit 開發的。在這裡,我們主要學習關于 WebKit 的這部分渲染引擎内容。

怎麼設定div為資料渲染後的高度_前端面試大全:浏覽器渲染原理浏覽器接收到 HTML 檔案并轉換為 DOM 樹将 CSS 檔案轉換為 CSSOM 樹生成渲染樹為什麼操作 DOM 慢什麼情況阻塞渲染重繪(Repaint)和回流(Reflow)思考題小結

浏覽器接收到 HTML 檔案并轉換為 DOM 樹

當我們打開一個網頁時,浏覽器都會去請求對應的 HTML 檔案。雖然平時我們寫代碼時都會分為 JS、CSS、HTML 檔案,也就是字元串,但是計算機硬體是不了解這些字元串的,是以在網絡中傳輸的内容其實都是 0 和 1 這些位元組資料。當浏覽器接收到這些位元組資料以後,它會将這些位元組資料轉換為字元串,也就是我們寫的代碼。

怎麼設定div為資料渲染後的高度_前端面試大全:浏覽器渲染原理浏覽器接收到 HTML 檔案并轉換為 DOM 樹将 CSS 檔案轉換為 CSSOM 樹生成渲染樹為什麼操作 DOM 慢什麼情況阻塞渲染重繪(Repaint)和回流(Reflow)思考題小結

當資料轉換為字元串以後,浏覽器會先将這些字元串通過詞法分析轉換為标記(token),這一過程在詞法分析中叫做标記化(tokenization)。

怎麼設定div為資料渲染後的高度_前端面試大全:浏覽器渲染原理浏覽器接收到 HTML 檔案并轉換為 DOM 樹将 CSS 檔案轉換為 CSSOM 樹生成渲染樹為什麼操作 DOM 慢什麼情況阻塞渲染重繪(Repaint)和回流(Reflow)思考題小結

那麼什麼是标記呢?這其實屬于編譯原理這一塊的内容了。簡單來說,标記還是字元串,是構成代碼的最小機關。這一過程會将代碼分拆成一塊塊,并給這些内容打上标記,便于了解這些最小機關的代碼是什麼意思。

怎麼設定div為資料渲染後的高度_前端面試大全:浏覽器渲染原理浏覽器接收到 HTML 檔案并轉換為 DOM 樹将 CSS 檔案轉換為 CSSOM 樹生成渲染樹為什麼操作 DOM 慢什麼情況阻塞渲染重繪(Repaint)和回流(Reflow)思考題小結

當結束标記化後,這些标記會緊接着轉換為 Node,最後這些 Node 會根據不同 Node 之前的聯系建構為一顆 DOM 樹。

怎麼設定div為資料渲染後的高度_前端面試大全:浏覽器渲染原理浏覽器接收到 HTML 檔案并轉換為 DOM 樹将 CSS 檔案轉換為 CSSOM 樹生成渲染樹為什麼操作 DOM 慢什麼情況阻塞渲染重繪(Repaint)和回流(Reflow)思考題小結

以上就是浏覽器從網絡中接收到 HTML 檔案然後一系列的轉換過程。

怎麼設定div為資料渲染後的高度_前端面試大全:浏覽器渲染原理浏覽器接收到 HTML 檔案并轉換為 DOM 樹将 CSS 檔案轉換為 CSSOM 樹生成渲染樹為什麼操作 DOM 慢什麼情況阻塞渲染重繪(Repaint)和回流(Reflow)思考題小結

當然,在解析 HTML 檔案的時候,浏覽器還會遇到 CSS 和 JS 檔案,這時候浏覽器也會去下載下傳并解析這些檔案,接下來就讓我們先來學習浏覽器如何解析 CSS 檔案。

将 CSS 檔案轉換為 CSSOM 樹

其實轉換 CSS 到 CSSOM 樹的過程和上一小節的過程是極其類似的

怎麼設定div為資料渲染後的高度_前端面試大全:浏覽器渲染原理浏覽器接收到 HTML 檔案并轉換為 DOM 樹将 CSS 檔案轉換為 CSSOM 樹生成渲染樹為什麼操作 DOM 慢什麼情況阻塞渲染重繪(Repaint)和回流(Reflow)思考題小結

在這一過程中,浏覽器會确定下每一個節點的樣式到底是什麼,并且這一過程其實是很消耗資源的。因為樣式你可以自行設定給某個節點,也可以通過繼承獲得。在這一過程中,浏覽器得遞歸 CSSOM 樹,然後确定具體的元素到底是什麼樣式。

如果你有點不了解為什麼會消耗資源的話,我這裡舉個例子

對于第一種設定樣式的方式來說,浏覽器隻需要找到頁面中所有的 span 标簽然後設定顔色,但是對于第二種設定樣式的方式來說,浏覽器首先需要找到所有的 span 标簽,然後找到 span 标簽上的 a 标簽,最後再去找到 div 标簽,然後給符合這種條件的 span 标簽設定顔色,這樣的遞歸過程就很複雜。是以我們應該盡可能的避免寫過于具體的 CSS 選擇器,然後對于 HTML 來說也盡量少的添加無意義标簽,保證層級扁平。

生成渲染樹

當我們生成 DOM 樹和 CSSOM 樹以後,就需要将這兩棵樹組合為渲染樹。

怎麼設定div為資料渲染後的高度_前端面試大全:浏覽器渲染原理浏覽器接收到 HTML 檔案并轉換為 DOM 樹将 CSS 檔案轉換為 CSSOM 樹生成渲染樹為什麼操作 DOM 慢什麼情況阻塞渲染重繪(Repaint)和回流(Reflow)思考題小結

在這一過程中,不是簡單的将兩者合并就行了。渲染樹隻會包括需要顯示的節點和這些節點的樣式資訊,如果某個節點是 display: none 的,那麼就不會在渲染樹中顯示。

當浏覽器生成渲染樹以後,就會根據渲染樹來進行布局(也可以叫做回流),然後調用 GPU 繪制,合成圖層,顯示在螢幕上。對于這一部分的内容因為過于底層,還涉及到了硬體相關的知識,這裡就不再繼續展開内容了。

那麼通過以上内容,我們已經詳細了解到了浏覽器從接收檔案到将内容渲染在螢幕上的這一過程。接下來,我們将會來學習上半部分遺留下來的一些知識點。

為什麼操作 DOM 慢

想必大家都聽過操作 DOM 性能很差,但是這其中的原因是什麼呢?

因為 DOM 是屬于渲染引擎中的東西,而 JS 又是 JS 引擎中的東西。當我們通過 JS 操作 DOM 的時候,其實這個操作涉及到了兩個線程之間的通信,那麼勢必會帶來一些性能上的損耗。操作 DOM 次數一多,也就等同于一直在進行線程之間的通信,并且操作 DOM 可能還會帶來重繪回流的情況,是以也就導緻了性能上的問題。

經典面試題:插入幾萬個 DOM,如何實作頁面不卡頓?

對于這道題目來說,首先我們肯定不能一次性把幾萬個 DOM 全部插入,這樣肯定會造成卡頓,是以解決問題的重點應該是如何分批次部分渲染 DOM。大部分人應該可以想到通過 requestAnimationFrame的方式去循環的插入 DOM,其實還有種方式去解決這個問題:虛拟滾動(virtualized scroller)。

這種技術的原理就是隻渲染可視區域内的内容,非可見區域的那就完全不渲染了,當使用者在滾動的時候就實時去替換渲染的内容。

怎麼設定div為資料渲染後的高度_前端面試大全:浏覽器渲染原理浏覽器接收到 HTML 檔案并轉換為 DOM 樹将 CSS 檔案轉換為 CSSOM 樹生成渲染樹為什麼操作 DOM 慢什麼情況阻塞渲染重繪(Repaint)和回流(Reflow)思考題小結

從上圖中我們可以發現,即使清單很長,但是渲染的 DOM 元素永遠隻有那麼幾個,當我們滾動頁面的時候就會實時去更新 DOM,這個技術就能順利解決這道經典面試題。如果你想了解更多的内容可以了解下這個 react-virtualized。

什麼情況阻塞渲染

首先渲染的前提是生成渲染樹,是以 HTML 和 CSS 肯定會阻塞渲染。如果你想渲染的越快,你越應該降低一開始需要渲染的檔案大小,并且扁平層級,優化選擇器。

然後當浏覽器在解析到 script 标簽時,會暫停建構 DOM,完成後才會從暫停的地方重新開始。也就是說,如果你想首屏渲染的越快,就越不應該在首屏就加載 JS 檔案,這也是都建議将 script 标簽放在 body 标簽底部的原因。

當然在當下,并不是說 script 标簽必須放在底部,因為你可以給 script 标簽添加 defer 或者 async 屬性。

當 script 标簽加上 defer 屬性以後,表示該 JS 檔案會并行下載下傳,但是會放到 HTML 解析完成後順序執行,是以對于這種情況你可以把 script 标簽放在任意位置。

對于沒有任何依賴的 JS 檔案可以加上 async 屬性,表示 JS 檔案下載下傳和解析不會阻塞渲染。

重繪(Repaint)和回流(Reflow)

重繪和回流會在我們設定節點樣式時頻繁出現,同時也會很大程度上影響性能。

  • 重繪是當節點需要更改外觀而不會影響布局的,比如改變 color 就叫稱為重繪
  • 回流是布局或者幾何屬性需要改變就稱為回流。

回流必定會發生重繪,重繪不一定會引發回流。回流所需的成本比重繪高的多,改變父節點裡的子節點很可能會導緻父節點的一系列回流。

以下幾個動作可能會導緻性能問題:

  • 改變 window 大小
  • 改變字型
  • 添加或删除樣式
  • 文字改變
  • 定位或者浮動
  • 盒模型

并且很多人不知道的是,重繪和回流其實也和 Eventloop 有關。

  1. 當 Eventloop 執行完 Microtasks 後,會判斷 document 是否需要更新,因為浏覽器是 60Hz 的重新整理率,每 16.6ms 才會更新一次。
  2. 然後判斷是否有 resize 或者 scroll 事件,有的話會去觸發事件,是以 resize 和 scroll 事件也是至少 16ms 才會觸發一次,并且自帶節流功能。
  3. 判斷是否觸發了 media query
  4. 更新動畫并且發送事件
  5. 判斷是否有全屏操作事件
  6. 執行 requestAnimationFrame 回調
  7. 執行 IntersectionObserver 回調,該方法用于判斷元素是否可見,可以用于懶加載上,但是相容性不好
  8. 更新界面
  9. 以上就是一幀中可能會做的事情。如果在一幀中有空閑時間,就會去執行 requestIdleCallback回調。

以上内容來自于 HTML 文檔。

既然我們已經知道了重繪和回流會影響性能,那麼接下來我們将會來學習如何減少重繪和回流的次數。

減少重繪和回流

  • 使用 transform 替代 top
怎麼設定div為資料渲染後的高度_前端面試大全:浏覽器渲染原理浏覽器接收到 HTML 檔案并轉換為 DOM 樹将 CSS 檔案轉換為 CSSOM 樹生成渲染樹為什麼操作 DOM 慢什麼情況阻塞渲染重繪(Repaint)和回流(Reflow)思考題小結
  • 使用 visibility 替換 display: none ,因為前者隻會引起重繪,後者會引發回流(改變了布局)
  • 不要把節點的屬性值放在一個循環裡當成循環裡的變量
for(let i = 0; i < 1000; i++) { // 擷取 offsetTop 會導緻回流,因為需要去擷取正确的值 console.log(document.querySelector('.test').style.offsetTop)}
           
  • 不要使用 table 布局,可能很小的一個小改動會造成整個 table 的重新布局
  • 動畫實作的速度的選擇,動畫速度越快,回流次數越多,也可以選擇使用 requestAnimationFrame
  • CSS 選擇符從右往左比對查找,避免節點層級過多
  • 将頻繁重繪或者回流的節點設定為圖層,圖層能夠阻止該節點的渲染行為影響别的節點。比如對于 video 标簽來說,浏覽器會自動将該節點變為圖層。
怎麼設定div為資料渲染後的高度_前端面試大全:浏覽器渲染原理浏覽器接收到 HTML 檔案并轉換為 DOM 樹将 CSS 檔案轉換為 CSSOM 樹生成渲染樹為什麼操作 DOM 慢什麼情況阻塞渲染重繪(Repaint)和回流(Reflow)思考題小結
  • 設定節點為圖層的方式有很多,我們可以通過以下幾個常用屬性可以生成新圖層
  • will-change
  • video、iframe 标簽

思考題

思考題:在不考慮緩存和優化網絡協定的前提下,考慮可以通過哪些方式來最快的渲染頁面,也就是常說的關鍵渲染路徑,這部分也是性能優化中的一塊内容。

首先你可能會疑問,那怎麼測量到底有沒有加快渲染速度呢

怎麼設定div為資料渲染後的高度_前端面試大全:浏覽器渲染原理浏覽器接收到 HTML 檔案并轉換為 DOM 樹将 CSS 檔案轉換為 CSSOM 樹生成渲染樹為什麼操作 DOM 慢什麼情況阻塞渲染重繪(Repaint)和回流(Reflow)思考題小結

當發生 DOMContentLoaded 事件後,就會生成渲染樹,生成渲染樹就可以進行渲染了,這一過程更大程度上和硬體有關系了。

提示如何加速:

  1. 從檔案大小考慮
  2. 從 script 标簽使用上來考慮
  3. 從 CSS、HTML 的代碼書寫上來考慮
  4. 從需要下載下傳的内容是否需要在首屏使用上來考慮

以上提示大家都可以從文中找到,同時也歡迎大家踴躍在評論區寫出你的答案。

小結

以上就是我們這一部分的内容了。在這一篇文章中,我們了解了浏覽器如何将檔案渲染為頁面,同時也掌握了一些優化的小技巧。這部分的内容了解起來不大容易,希望大夥能自己再去查閱資料詳細的了解。