前端分享會--重排、重繪、阻塞
《前端分享會》將是本坑新開的專題,這個專題主要是平時本坑在單位分享會分享的,和平時看到想分享的內容。涉及的內容可以是比較簡單,初級。當然也會有程度比較高知識分享。
可能看官覺得,太基礎和太簡單的內容有什麼好分享的。非也!很多時候,可能理所當然,然而當面試問這些問題時,你會發現這些簡單知識點距離如此之近而你卻打不出其中原委。實踐證明,很多基礎知識點蘊藏著開啟新世界的鑰匙。理解之,能解開你在一系列建立在該基礎上的技術實現給你帶來的疑問。
即使,不是這樣,多多回顧甚至背誦這些基礎知識點的來龍去脈,可以輕鬆應對面試題的絕大部分。
今天來聊聊重排和重繪。重排在一些文章中也叫回流。
重排和重繪
從字面意思來看重排就是重新排列,而重繪就是重新繪製。很顯然既然是重新排列,自然就要重新繪。重排和重繪都是是根據 dom
元素來說的?其實並不完全是。而是 render tree
的節點。說到這裡,我們要回顧下瀏覽器渲染一個頁面的過程是怎麼樣的。

將 HTML
構建成一個 DOM
樹( DOM = Document Object Model
文件物件模型), DOM
樹的構建過程是一個深度遍歷過程:當前節點的所有子節點都構建好後才會去構建當前節點的下一個兄弟節點。
將 CSS
解析成 CSS
去構造 CSSOM
樹( CSSOM
= CSS Object Model CSS
物件模型)
根據 DOM
樹和 CSSOM
來構造 Rendering Tree
(渲染樹)。注意: Rendering Tree
渲染樹並不等同於 DOM
樹,因為一些像 Header 或 display:none 的東西就沒必要放在渲染樹中了。
有了 Render Tree
,瀏覽器已經能知道網頁中有哪些節點、各個節點的 CSS
定義以及他們的從屬關係。
再來就是 Layout
,顧名思義就是計算出每個節點在螢幕中的位置 layout render tree
。
而後就是繪製,即遍歷 render
樹,並使用瀏覽器 UI
後端層繪製每個節點。
在整個過程中, javascript
可能會改變 dom
樹和 cssom
的結構,所以瀏覽器往往會等待js的載入,因此擱置整個渲染數的構建。
不同瀏覽器對佈局和繪製的順序可以是不同的,根據不同的瀏覽器優化策略,瀏覽器能夠更好的協調佈局和繪製過程。
而我們說重繪和重排,就是重新去佈局的過程和重新繪製的過程。
渲染樹的變更會導致重排,而渲染數的節點的屬性變化回導致重繪。
阻塞
從上面的過程,我們要特別要知道一點: dom
節點是邊載入邊渲染的。同時css資源和js資源也是同時在載入,文件邊渲染的。
由此就會帶來一些阻塞問題,也就是使用者看到白屏。比如:
如果 dom
節點中涉及到資源載入會影響到 dom
的渲染。像 js
載入和執行會影響 dom
樹的解析。 css
載入會影響到 cssom
的形成,但是不影響 dom
樹的形成。所以js資源我們會放到文件末尾的原因,而樣式是放在前面。
不管是js資源也好還是 css
資源也好,我們都要求儘量壓縮和小,也是同一個道理。
總結一下, DOM
樹的構建會被阻塞:
HTML
的響應流被阻塞在了網路中
有未載入完的指令碼
遇到了 script
節點,但是此時還有未載入完的樣式檔案, dom
解析不受到影響
下面有個例子( 來自網路,地址在文章底部
)
<html> <body> <link rel="stylesheet" href="example.css"> <div>Hi there!</div> <script> document.write('<script src="other.js"></scr' + 'ipt>'); </script> <div>Hi again!</div> <script src="last.js"></script> </body> </html>
<html> <body> <link rel="stylesheet" href="example.css"> <div>Hi there!</div> <script>...
首先,解析器遇到了 example.css
,並將它從網路中下載下來。下載樣式表的過程是耗時的,但是解析器並沒有被阻塞,繼續往下解析。接下來,解析器遇到 script
標籤,但是由於樣式檔案沒有載入下來,阻塞了該指令碼的執行。解析器被阻塞住,不能繼續往下解析。
渲染樹也會被樣式檔案阻塞,所以這時候沒有瀏覽器不會去渲染頁面,換句話說,如果 example.css
檔案下載不下來, Hi there!
是顯示不出來的。
<html> <body> <link rel="stylesheet" href="example.css"> <div>Hi there!</div> <script> document.write('<script src="other.js"></scr' + 'ipt>'); </script>
一旦 example.css
檔案載入完成,渲染樹也就被構建好了。
內聯的指令碼執行完之後,解析器就會立即被 other.js
阻塞住。一旦解析器被阻塞,瀏覽器就會收到繪製請求, "Hi there!"
也就顯示在了頁面上。
當 other.js
載入完成之後,解析器繼續向下解析。。。
<html> <body> <link rel="stylesheet" href="example.css"> <div>Hi there!</div> <script> document.write('<script src="other.js"></scr' + 'ipt>'); </script> <div>Hi again!</div> <script src="last.js"></script>
解析器遇到 last.js
之後會被阻塞,然後瀏覽器收到了另一個繪製請求, "Hi again!"
就顯示在了頁面上。最後last.js會被載入,並且會被執行。