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