前言
當我們在瀏覽器中輸入www.google.com時,他會發生什麼?他總共會經歷兩個過程,一個是瀏覽器的請求過程,還有一個是瀏覽器的渲染過程,今天我們簡單聊聊瀏覽器的渲染過程。
瀏覽器的渲染過程
當我們瀏覽器拿到請求過來的資源時,首先會將請求資料包的二進制位元組資料檔案解析為html和css字串檔案,然後瀏覽器拿到這些字串資料之後會給這些資料打上token標記即最小程式碼單位,目的是爲了讓瀏覽器更好的分割程式碼理解語句,打上標記之後緊接著就會生成一些node節點,然後根據這些節點構建dom樹,自此瀏覽器就拿到了一顆dom樹。然後瀏覽器會拿著css字串檔案生成一顆cssom樹。現在我們就有兩顆樹,瀏覽器會拿著這兩棵樹去生成一顆rander樹,一個小細節,渲染樹只會包含顯示的節點,也就是說當一段html結構被設定成display為none時,他是不會再渲染樹中出現的。然後瀏覽器就拿著這顆渲染樹去計算佈局也叫做迴流,就是計算元素的位置和大小的過程,然後開始繪製也叫做重繪,即當元素的外觀發生變化(如顏色、背景影象、邊框樣式等)時,瀏覽器重新繪製元素的過程。自此瀏覽器的渲染過程到此結束,這是比較簡短的描述,裡面還包含著非常複雜的過程。
分點方便記憶的話就是這樣
解析資料包得到htlm檔案,css檔案(將二進制的位元組資料解析成字串)
位元組資料-->字串-->token標記-->node節點-->構建dom樹
將css檔案轉化為cssom樹
將dom樹和cssom樹進行合併,得到render樹(渲染樹只會包含顯示的節點)display:none的節點不會被渲染
計算佈局(迴流)
GPU繪製(重繪)
在我們瞭解瀏覽器的渲染過程之後,我們可以透過一些規範技巧來提升我們的程式碼效能。
最佳化程式碼
為什麼操作dom慢?
我們在學習vue的時候常說,不要在程式碼裡面操作dom元素,因為操作dom是一件非常消耗效能的行為,但是為什麼呢?在瀏覽器中會有兩個執行緒,一個是js引擎執行緒,一個是渲染執行緒,這兩個執行緒他是互斥的,為什麼要互斥?我們想象一下,當我們在js中操作樣式時,把一個盒子的寬度設定為100px,但是我在css中也設定了50px,當這兩個執行緒同時操作這個盒子的寬度時,那就會亂套,瀏覽器不知道最終盒子的寬度設定為多少,所以這兩個執行緒一定會互斥,也就是說js引擎執行緒會阻塞渲染執行緒的載入。結論,當我們頻繁的在程式碼裡面操作dom元素時,就勢必會涉及到兩個執行緒的通訊和切換,造成效能上的損耗。
所以第一個可以最佳化的點就是儘量減少操作dom元素。
減少迴流重繪次數
迴流是一個開銷較大的操作,因為瀏覽器需要重新計算頁面佈局,可能會導致效能問題,尤其是在包含大量 DOM 元素或頻繁觸發迴流的情況下。因此,在開發網頁時,儘量減少不必要的迴流操作是很重要的。
雖然重繪的開銷較小,但頻繁的重繪操作仍然可能影響頁面效能。因此,在開發網頁時,同樣需要注意減少不必要的重繪操作。
什麼情況下會觸發迴流
新增或刪除 DOM 元素:在頁面中新增或刪除元素會導致迴流。
修改元素的尺寸、邊距、填充或邊框:任何這些屬性的變化都可能導致迴流。
修改元素的內容:例如改變文字內容的大小或新增圖片。
調整視窗大小:當用戶調整瀏覽器視窗的大小時,頁面佈局需要重新計算。
獲取佈局資訊:訪問某些屬性(如 offsetWidth、offsetHeight、clientWidth、clientHeight 等)時,瀏覽器可能需要執行迴流以確保返回的值是最新的。
CSS 樣式的改變:修改影響佈局的 CSS 樣式屬性(如 display、position、width、height 等)也會觸發迴流。
迴流必定會觸發重繪,當時重繪不一定會觸發迴流。
什麼情況下會觸發重繪
改變元素的顏色:例如改變文字顏色、背景顏色或邊框顏色。
改變元素的可見性:例如使用 visibility 屬性切換元素的顯示狀態(與 display 不同,display 會觸發迴流)。
改變元素的背景影象:例如更新背景影象或背景影象的位置。
改變元素的邊框樣式:例如修改邊框的樣式、顏色或寬度。
瀏覽器中迴流重繪的最佳化
瀏覽器會維護一個渲染佇列,當迴流發生時,迴流行為會被加入到佇列中,在達到閾值或者一定時間之後會一次性渲染佇列中的所有迴流生效。
當在js程式碼中寫下這些屬性時,會強制重新整理渲染佇列,當渲染佇列裡面有迴流時,就會使迴流生效,如果沒有就僅僅只是重新整理渲染佇列,而不會觸發迴流。
offsetWidth/offsetHeight/offsetTop/offsetLeft
clientWidth/clientHeight/clientTop/clientLeft
scorollWidth/scrollHeight/scrollTop/scrollLeft
這個時候就不得不提到位元組的一道面試題了,問觸發了幾次迴流重繪?你知道嗎?歡迎評論區留言奧。
let box = document.getElementById('box'); box.style.width = (box.offsetWidth+1)+'px'; box.style.width = 100+'px';
程式碼的最佳化
讓需要修改幾何屬性的容器先脫離文件流不顯示,修改完幾何屬性之後再顯示。
還記得我們之前說過,當一段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';
藉助文件碎片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); }