切換語言為:簡體

從瀏覽器渲染的角度分析程式碼的最佳化空間

  • 爱糖宝
  • 2024-07-20
  • 2062
  • 0
  • 0

前言

當我們在瀏覽器中輸入www.google.com時,他會發生什麼?他總共會經歷兩個過程,一個是瀏覽器的請求過程,還有一個是瀏覽器的渲染過程,今天我們簡單聊聊瀏覽器的渲染過程。

瀏覽器的渲染過程

當我們瀏覽器拿到請求過來的資源時,首先會將請求資料包的二進制位元組資料檔案解析為html和css字串檔案,然後瀏覽器拿到這些字串資料之後會給這些資料打上token標記即最小程式碼單位,目的是爲了讓瀏覽器更好的分割程式碼理解語句,打上標記之後緊接著就會生成一些node節點,然後根據這些節點構建dom樹,自此瀏覽器就拿到了一顆dom樹。然後瀏覽器會拿著css字串檔案生成一顆cssom樹。現在我們就有兩顆樹,瀏覽器會拿著這兩棵樹去生成一顆rander樹,一個小細節,渲染樹只會包含顯示的節點,也就是說當一段html結構被設定成display為none時,他是不會再渲染樹中出現的。然後瀏覽器就拿著這顆渲染樹去計算佈局也叫做迴流,就是計算元素的位置和大小的過程,然後開始繪製也叫做重繪,即當元素的外觀發生變化(如顏色、背景影象、邊框樣式等)時,瀏覽器重新繪製元素的過程。自此瀏覽器的渲染過程到此結束,這是比較簡短的描述,裡面還包含著非常複雜的過程。

分點方便記憶的話就是這樣

  1. 解析資料包得到htlm檔案,css檔案(將二進制的位元組資料解析成字串)

  2. 位元組資料-->字串-->token標記-->node節點-->構建dom樹

  3. 將css檔案轉化為cssom樹

  4. 將dom樹和cssom樹進行合併,得到render樹(渲染樹只會包含顯示的節點)display:none的節點不會被渲染

  5. 計算佈局(迴流)

  6. GPU繪製(重繪)

在我們瞭解瀏覽器的渲染過程之後,我們可以透過一些規範技巧來提升我們的程式碼效能。

最佳化程式碼

為什麼操作dom慢?

我們在學習vue的時候常說,不要在程式碼裡面操作dom元素,因為操作dom是一件非常消耗效能的行為,但是為什麼呢?在瀏覽器中會有兩個執行緒,一個是js引擎執行緒,一個是渲染執行緒,這兩個執行緒他是互斥的,為什麼要互斥?我們想象一下,當我們在js中操作樣式時,把一個盒子的寬度設定為100px,但是我在css中也設定了50px,當這兩個執行緒同時操作這個盒子的寬度時,那就會亂套,瀏覽器不知道最終盒子的寬度設定為多少,所以這兩個執行緒一定會互斥,也就是說js引擎執行緒會阻塞渲染執行緒的載入。結論,當我們頻繁的在程式碼裡面操作dom元素時,就勢必會涉及到兩個執行緒的通訊和切換,造成效能上的損耗。

所以第一個可以最佳化的點就是儘量減少操作dom元素。

減少迴流重繪次數

迴流是一個開銷較大的操作,因為瀏覽器需要重新計算頁面佈局,可能會導致效能問題,尤其是在包含大量 DOM 元素或頻繁觸發迴流的情況下。因此,在開發網頁時,儘量減少不必要的迴流操作是很重要的。

雖然重繪的開銷較小,但頻繁的重繪操作仍然可能影響頁面效能。因此,在開發網頁時,同樣需要注意減少不必要的重繪操作。

什麼情況下會觸發迴流
  1. 新增或刪除 DOM 元素:在頁面中新增或刪除元素會導致迴流。

  2. 修改元素的尺寸、邊距、填充或邊框:任何這些屬性的變化都可能導致迴流。

  3. 修改元素的內容:例如改變文字內容的大小或新增圖片。

  4. 調整視窗大小:當用戶調整瀏覽器視窗的大小時,頁面佈局需要重新計算。

  5. 獲取佈局資訊:訪問某些屬性(如 offsetWidth、offsetHeight、clientWidth、clientHeight 等)時,瀏覽器可能需要執行迴流以確保返回的值是最新的。

  6. CSS 樣式的改變:修改影響佈局的 CSS 樣式屬性(如 display、position、width、height 等)也會觸發迴流。

迴流必定會觸發重繪,當時重繪不一定會觸發迴流。

什麼情況下會觸發重繪
  1. 改變元素的顏色:例如改變文字顏色、背景顏色或邊框顏色。

  2. 改變元素的可見性:例如使用 visibility 屬性切換元素的顯示狀態(與 display 不同,display 會觸發迴流)。

  3. 改變元素的背景影象:例如更新背景影象或背景影象的位置。

  4. 改變元素的邊框樣式:例如修改邊框的樣式、顏色或寬度。

瀏覽器中迴流重繪的最佳化

瀏覽器會維護一個渲染佇列,當迴流發生時,迴流行為會被加入到佇列中,在達到閾值或者一定時間之後會一次性渲染佇列中的所有迴流生效。

當在js程式碼中寫下這些屬性時,會強制重新整理渲染佇列,當渲染佇列裡面有迴流時,就會使迴流生效,如果沒有就僅僅只是重新整理渲染佇列,而不會觸發迴流。

  1. offsetWidth/offsetHeight/offsetTop/offsetLeft

  2. clientWidth/clientHeight/clientTop/clientLeft

  3. scorollWidth/scrollHeight/scrollTop/scrollLeft

這個時候就不得不提到位元組的一道面試題了,問觸發了幾次迴流重繪?你知道嗎?歡迎評論區留言奧。

let box = document.getElementById('box');
box.style.width = (box.offsetWidth+1)+'px';
box.style.width = 100+'px';

程式碼的最佳化

  1. 讓需要修改幾何屬性的容器先脫離文件流不顯示,修改完幾何屬性之後再顯示。

還記得我們之前說過,當一段html設定display為none時,他是不會再渲染樹中出現的。所以也就不會執行緒互斥了,所以我們可以這麼處理。

let ul = document.querySelector('ul');
ul.style.display = 'none';
for(let i=0;i<10000;i++){
     let li = document.createElement('li');
     li.innerHTML = '我是第'+i+'個li';
     ul.appendChild(li);
}
ul.style.display = 'block';

  1. 藉助文件碎片Fragment

“文件碎片”(Document Fragment)是一個輕量級的、沒有父級的節點,可以用來包含其他節點。它作為一個臨時的容器,可以幫助最佳化 DOM 操作。使用文件碎片可以減少直接對 DOM 的多次操作,從而提高效能。

let frg = document.createDocumentFragment();
let ul = document.querySelector('ul');
for(let i=0;i<10000;i++){
     let li = document.createElement('li');
     li.innerHTML = '我是第'+i+'個li';
     frg.appendChild(li);
}

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.