大家好,我是桃翁
最近在面試的時候經常會問:如何理解重排和重繪?
我發現很多候選人都沒有答道關鍵點上,感覺是在哪裡看到過相關的文章,聽起來零零散散,毫無邏輯。
錯誤示範
一般的面試過程就是這樣的:
面試官:如何理解重排和重繪?
候選人:重排就是當頁面的結構發生變化了,就會重排,比如改變變字型的大小,增刪 DOM 元素這樣的。重繪就是頁面結構沒有變化,只是外觀變了,比如改了一下字型顏色、背景顏色這樣的。就只會發生重繪。
當然他說的也沒錯,我也不能直接說他錯,就繼續引導
面試官:那重排和重繪有什麼關係嗎?
候選人:重排一定會導致重繪,重繪不一定會導致重排。
面試官:為什麼呢?
候選人:因為重排結構發生變化了嘛,肯定會導致重繪。
我這時候表情就是這樣:
如果你覺得上面的回答很真實,那下面的你確定得好好看看。
接下來一般我不會直接跳過,我會再問一下瀏覽器關鍵渲染路徑引導一下。
如果不知道的話,我會再引導一下(這個時候其實基本已經放棄了)。
問一下你知道當瀏覽器載入到一個 HTML 會發生什麼事情嗎?
如果還是不知道的話,這下一題了。
如果知道關鍵渲染路徑的,基本引導一下還是可以搞明白,如果不清楚的,肯定是理解不了重排和重繪的。
考點
這道題我一般考察兩個點:
- 瀏覽器的關鍵渲染路徑。如果答不到這上面,一般這個題就涼了。
- 效能優化,如果減少重繪和迴流,當然這個點肯定也是要基於對 關鍵渲染路徑 的理解(這點不是關鍵點)。
複習
複習的目的是為了知道考點是啥,簡單的給大家複習一下,更詳細的內容希望按我介紹的知識點(可以看我文末推薦的文章進行深入學習),畢竟複習不是上課。
我們可以能知道,寫了 HTML、CSS、JavaScript 就可以將頁面渲染到螢幕上,但是瀏覽器是如何把我們的程式碼渲染到螢幕上的畫素點的呢?這就需要了解到這麼一個概念 CRP:
關鍵渲染路徑(Critical Rendering Path)是瀏覽器將 HTML,CSS 和 JavaScript 轉換為螢幕上的畫素所經歷的步驟序列。優化關鍵渲染路徑可提高渲染效能。
大致步驟是這樣:在解析 HTML 時會建立 DOM,HTML 可以請求 JavaScript,而 JavaScript 反過來,又可以更改 DOM。HTML 包含或請求樣式,依次來構建 CSSOM。
瀏覽器引擎將兩者結合起來以建立 Render Tree (渲染樹),Layout(佈局)確定頁面上所有內容的大小和位置,確定佈局後,將畫素 Paint (繪製)到螢幕上。
優化關鍵渲染路徑可以縮短首次渲染的時間。瞭解和優化關鍵渲染路徑對於確保重排和重繪可以每秒 60 幀的速度進行,以確保高效的使用者互動並避免討厭是很重要的。
接下來研究一下詳細的過程:
步驟
1. 生成 DOM
DOM 構建是增量的。瀏覽器從遠端下載 Byte
=> 根據相應的編碼 (如 utf8) 轉化為字串
=> 通過 AST 解析為 Token
=> 生成 Node
=> 生成 DOM
。
單個 DOM 節點以 startTag token 開始,以 endTag token 結束。 節點包含有關 HTML 元素的所有相關資訊。 該資訊是使用 token 描述的。 節點根據 token 層次結構連線到 DOM 樹中。
如果另一組 startTag 和 endTag token 位於一組 startTag 和 endTag 之間,則在節點內有一個節點,這就是我們定義 DOM 樹層次結構的方式。
2. 生成 CSSOM
瀏覽器解析 css 檔案,生成 CSSOM。CSSOM 包含了頁面所有的樣式,也就是如何展示 DOM 的資訊。
CSSOM 跟 DOM 很像,但是不同。
DOM 構造是增量的,CSSOM 卻不是。CSS 是渲染阻塞的:瀏覽器會阻塞頁面渲染直到它接收和執行了所有的 CSS。
CSS 是渲染阻塞是因為規則可以被覆蓋,所以內容不能被渲染直到 CSSOM 的完成。
3. Render Tree
渲染樹(Render Tree)包括了內容和樣式:DOM 和 CSSOM 樹結合為渲染樹。
為了構造渲染樹,瀏覽器檢查每個節點,從 DOM 樹的根節點開始,並且決定哪些 CSS 規則被新增。
渲染樹只包含了可見內容(body 裡的部分)。
Head(通常)不包含任何可見資訊,因此不會被包含在渲染樹種。如果有元素上有 display: none;
,它本身和其後代都不會出現在渲染樹中。
4. Layout
一旦渲染樹被構建,佈局變成了可能。佈局取決於螢幕的尺寸。佈局這個步驟決定了在哪裡和如何在頁面上放置元素,決定了每個元素的寬和高,以及他們之間的相關性。
提示:一個頁面渲染在不同尺寸的螢幕上,比如渲染在移動端和 PC 端上,展示有差異,在前面的步驟都是不變的,只有在佈局的時候才會根據螢幕尺寸進行差異化處理。
5. Paint
最後一步是將畫素繪製在螢幕上,柵格化所有元素,將元素轉換為實際畫素。
一旦渲染樹建立並且佈局完成,畫素就可以被繪製在螢幕上。載入時,整個螢幕被繪製出來。之後,只有受影響的螢幕區域會被重繪,瀏覽器被優化為只重繪需要繪製的最小區域。
繪製時間取決於何種型別的更新被附加在渲染樹上。繪製是一個非常快的過程,所以聚焦在提升效能時這大概不是最有效的部分
重排(Reflow)和重繪(Repaint)
瞭解完上面的關鍵路徑渲染之後,再來了解重排和重繪簡直就是小 case。
重排(Reflow):元素的 位置發生變動 時發生重排,也叫回流。此時在 Layout 階段,計算每一個元素在裝置視口內的確切位置和大小。當一個元素位置發生變化時,其父元素及其後邊的元素位置都可能發生變化,代價極高。
在回答什麼是重排的時候,關鍵不是位置發生變動,這只是原因(Why),而不是 What。What 是重新計算每個元素在裝置視口內的確切位置和大小。
重繪(Repaint): 元素的 樣式發生變動 ,但是位置沒有改變。此時在關鍵渲染路徑中的 Paint 階段,將渲染樹中的每個節點轉換成螢幕上的實際畫素,這一步通常稱為繪製或柵格化。
而回答什麼是重繪的關鍵點在於在關鍵渲染路徑中的 Paint 階段,將渲染樹中的每個節點轉換成螢幕上的實際畫素,這才是 What。
JavaScript 與關鍵路徑渲染
前面聊步驟的時候基本都是聊的 HTML 、CSS 與 CRP 的關係,最後再聊一下 JS 與 CRP 的關係,再看一下文章開頭的這個圖。
JavaScript 的執行是在生成渲染樹之前的。這也是為什麼 JavaScript 的載入、解析與執行會阻塞 DOM 的構建,阻塞頁面的渲染。
這其實是非常合理的
因為 JavaScript 可以修改網頁的內容,它可以更改 DOM,如果不阻塞,那麼這邊在構建 DOM,那邊 JavaScript 在改 DOM,如何保障最終得到的 DOM 是否正確?
而且在 JS 中前一秒獲取到的 DOM 和後一秒獲取到的 DOM 不一樣是什麼鬼?它會產生一系列問題,所以 JS 是阻塞的,它會阻塞 DOM 的構建流程,所以在 JS 中無法獲取 JS 後面的元素,因為 DOM 還沒構建到那。
這就是為什麼我們需要把 js 放在頁面底部的原因,儘量保證 DOM 樹生成完畢再去載入 JS,從而出現這樣的效果。
效能優化
基於以上的分析,簡單的說幾條效能優化的方式,自己可以去分析一下為什麼這些方式可以做效能優化。
- 減少 DOM 樹渲染時間(譬如降低 HTML 層級、標籤儘量語義化等等)
- 減少 CSSOM 樹渲染時間(降低選擇器層級等等)
- 減少 HTTP 請求次數及請求大小
- 將 css 放在頁面開始位置
- 將 js 放在頁面底部位置,並儘可能用 defer 或者 async 避免阻塞的 js 載入,確保 DOM 樹生成完才會去載入 JS
- 用 link 替代@import
- 如果頁面 css 較少,儘量使用內嵌式
- 為了減少白屏時間,頁面載入時先快速生成一個 DOM 樹
正確的效能優化思路
再囉嗦一下效能優化相關的,當你遇到一個效能問題的時候,絕對不是去網上找一些效能優化的方法,然後不管三七二十一,就整上去,這樣大概率是沒啥用的。
第一件事情,一定是要先分析效能的瓶頸在哪裡。
第一件事情,一定是要先分析效能的瓶頸在哪裡。
第一件事情,一定是要先分析效能的瓶頸在哪裡。
比如你遇到了首屏載入的效能問題,你就要根據開發者工具,比如看 network 是否是由於資源體積太大導致請求慢,還是後端處理慢,還是資源太多了載入慢.
如果這些都不是,可能是因為 渲染慢,再去分析 performce 皮膚,看一下是 js 執行慢,還是啥原因。
再比如你遇到了 webpack 的效能問題,比如打包的資源太大了,你要去解決這個問題,你也不應該直接去隨便找幾個優化的方法就開始整。
而是先應該通過 webpack-bundle-analyzer 外掛去分析包大的原因是什麼?
- 是依賴包太大了,沒有做按需載入?
- 還是重複的打包了幾個版本的相同依賴?
- 還是因為 src 太大了,是否需要做個動態載入?
- 還是因為其他的,通過 webpack-bundle-analyzer 分析出來的組成內容去找問題。
最後再總結一下,遇到問題應該先通過各種分析工具,找到出現效能瓶頸的原因,再根據這個原因去尋找對應的優化方式,要對症下藥。
不管是面試的時候回答,還是自己平時在處理問題的時候都要這樣,只有分析出問題了,解決方案一大堆,找不到問題,瞎搞就是浪費時間。
參考回答
我相信複習完之後,對這個知識點應該是清楚了,面試的時候不需要說這麼多,把關鍵點說出來,讓面試官知道你是懂的就行,如果面試官有興趣的話會繼續追問的,這個時候再詳細的跟他介紹追問的點。
如果是我被問到這個題,我的回答大概是這樣的,僅供參考:
重排和重繪是瀏覽器關鍵渲染路徑上的兩個節點, 瀏覽器的關鍵渲染路徑就是 DOM 和 CSSOM 生成渲染樹,然後根據渲染樹通過一個佈局(也叫 layout)步驟來確定頁面上所有內容的大小和位置,確定佈局後,將畫素 繪製 (也叫 Paint)到螢幕上。
其中重排就是當元素的位置發生變動的時候,瀏覽器重新執行佈局這個步驟,來重新確定頁面上內容的大小和位置,確定完之後就會進行重新繪製到螢幕上,所以重排一定會導致重繪。
如果元素位置沒有發生變動,僅僅只是樣式發生變動,這個時候瀏覽器重新渲染的時候會跳過佈局步驟,直接進入繪製步驟,這就是重繪,所以重繪不一定會導致重排。
上面這樣回答,我覺得在絕大部分面試官那裡已經可以拿到這個題的分了。
不過面試官還是有可能繼續往深的問,小夥伴們可以在評論區說一說你們還遇到過哪些相關的問題,我後面再繼續幫助大家一起分析。
對於效能問題上,減少重繪和迴流感覺沒有那麼重要,因為優化一般情況不是很明顯,不答問題也不大,更多的效能優化是在整個鏈路上的優化,比如效能優化標題裡面的那 8 個點。
最後
在行業不景氣的時候,希望大家都能順利找到合適的工作。
關於關鍵路徑渲染和重排、重繪,我最後還是忍不住給大家推薦一下大漠老師的兩篇文章,有空去拜讀一下子。