根據瀏覽器渲染流程研究「阻塞」

這篇文章源於公司內部的一次交流分享活動,選的主題都是些本身不瞭解的知識,而後經過學習研究再和同事分享。之因此選擇本身不瞭解的知識,也是爲了督促本身學習進步,而相應的因本人水平有限且初初接觸,文章中可能會存在某些不嚴謹的或者不正確的地方,望各位大神批評指正。原本定的主題是「預加載」,由於不瞭解,因此一再追本溯源,最後竟成了這關於瀏覽器渲染與阻塞的文章。css

瀏覽器渲染流程

一、概述

瀏覽器有許多模塊,其中負責呈現頁面的是渲染引擎模塊,下圖爲Webkit渲染流程:html


雖然不一樣瀏覽器內核渲染細節有所不一樣,但基本思路類似:node

  • 處理HTML標記並構建DOM樹。
  • 處理CSS標記並構建CSSOM樹。
  • DOM樹與CSSOM樹合併後造成渲染樹Render Tree(只包含渲染網頁所需的節點)。
  • layout佈局(reflow自動重排)結算節點在設備視口內的確切位置和大小。
  • painting繪製(rasterizing柵格化):結合layout將渲染樹中的各個節點繪製到屏幕上。

二、DOM和CSSOM樹的構建

DOM和CSSOM是獨立的數據結構,其解析是兩個並行的進程,所以CSS加載不會阻塞DOM的解析。web

DOM樹和CSSOM樹的構建過程相似,下圖爲DOM樹的構建過程:面試

字節 → 字符 → 令牌 → 節點 → 對象模型瀏覽器


  • 轉換(字節->字符):將原始字節轉換成字符;
  • 令牌化(字符->令牌):將字符轉換成W3C HTML5標準規定的令牌,也就是標籤,包括標籤及標籤內部字符串;
  • 詞法分析(令牌->節點):根據令牌生成包含其屬性和規則的「對象」;
  • DOM構建(節點->對象模型):節點間添加父子關係,造成樹形結構。

三、Render樹的生成

下圖爲渲染樹的生成過程:bash


注意,渲染樹只包含渲染網頁所需的節點微信

CSS和JS阻塞

一、實驗1——疑惑起

原文: zhuanlan.zhihu.com/p/24944905

用一句話歸納就是: JS 全阻塞,CSS 半阻塞。
數據結構

  1. JS 會阻塞後續 DOM 解析以及其它資源(如 CSS,JS 或圖片資源)的加載。
  2. CSS 不會阻塞後續 DOM 結構的解析,不會阻塞其它資源(如圖片)的加載,可是會阻塞 JS 文件的加載。
  3. 現代瀏覽器很聰明,會進行 prefetch 優化,瀏覽器在得到 html 文檔以後會對頁面上引用的資源進行提早下載。(注意僅僅只是提早下載)

這是關於」阻塞「我精讀的第一篇文章,當初吸引個人一是歸納得很通俗易懂(如上),二是提供了一種實驗方式,這種方式很便於經過實驗增強對阻塞的理解,更是我第一次寫Node.js。dom

下圖是實驗的運行方式,前提是已經裝了node。


文章中的實驗很簡單,分析的也頗有道理的樣子,我覺得這一篇就能幫我搞定對」阻塞「的理解。然而,問題來了,看着實驗結果,有點懵,感受」阻塞「更難懂了。下圖爲文章demo的實驗結果:


看了實驗結果,感受跟文章中的結論對不上啊,下面是我產生的一系列疑問:

  1. 彷佛css和js文件的加載都阻塞了後續dom解析和文件的加載(不論是css仍是js文件),好比red.css和green.css加載時明顯阻塞了DOM解析現象,後續dom(third line、fourth line)並無解析。
  2. 背景色發生屢次變化,也就是說過程當中發生了屢次渲染,dom還沒解析完畢,怎麼就渲染了呢?

二、實驗2——蒐集資料並分析問題

帶着疑惑我將index.html中的代碼作了以下修改,主要是按咱們更常見的方式將<link><script>標籤放在<head>標籤裏:

<head>    
    <script src="/yellow.js"></script>    <!--5s-->    
    <link rel="stylesheet" href="/red.css">    <!--15s-->   
    <script src="/blue.js"></script>    <!--10s-->   
    <link rel="stylesheet" href="/green.css">    <!--20s-->
</head>
<body>    
    <p>First Line</p>    
    <p>Second Line</p>   
    <p>Third Line</p>    
    <p>Fourth Line</p>    
    <p>Fifth Line</p>
</body>複製代碼

js文件裏的內容都是改變顏色,如blue.js:

document.body.style.cssText = "background: blue !important";複製代碼

實驗現象:


  1. 等待yellow.js加載,阻塞DOM解析
  2. red.css不阻塞後續解析,所以繼續解析到blue.js
  3. blue.js阻塞
  4. green.css不阻塞DOM解析,完成<body>裏內容的解析
  5. 等待green.css加載完成後渲染最終頁面。
這裏說明下最終頁面渲染成綠色的緣由,原本js定義的樣式是內聯樣式,優先級高於CSS文件中定義的樣式,但由於js執行時還沒有解析到body,document.body=null,沒法對其進行樣式設置。能夠參考實驗1一塊兒理解。


實驗分析

結合實驗2的實驗現象,和期間對相關資料的閱讀學習,對實驗1所在文章中的一些說法和實驗現象說說個人理解。

  1. 「JS 會阻塞後續 DOM 解析以及其它資源(如 CSS,JS 或圖片資源)的加載」——出自實驗1原文。
    其實js阻塞的就只是DOM的解析,緣由不少文章都有提,這裏不贅述,個人理解就是爲了不衝突。
    而「阻塞其餘資源的加載」一說則不夠準確,實際上就只是DOM解析終止,沒有將相關標籤好比<link>掛在DOM樹上,但這些資源的下載則一開始就開始了且與DOM解析並行。
  2. 實驗1發生屢次渲染現象,而實驗2只在DOM解析結束且CSS加載完畢後進行一次渲染,這是爲何呢?由於<body>裏 <link>,<script>標籤都會觸發一次渲染,都會阻塞dom的解析。
  3. 實驗2說明了js阻塞了DOM解析,CSS不阻塞DOM解析,但阻塞頁面渲染。那CSS阻塞js的執行嗎?請看實驗3。

三、實驗3——CSS阻塞JS運行

由於以前的js代碼都是經過link標籤引入的,這裏實驗3就直接將執行代碼寫進<script>標籤裏,但實際上,結果是同樣的。不少事情,只要明白原理,那麼就會一通百通啦~

<head>
    <link rel="stylesheet" href="/red.css"><!-- wait="15s"-->    
    <script>        
        console.log('hello')    
    </script>
</head>複製代碼


經過實驗3能夠看出CSS阻塞了js的執行,那麼這是爲何呢?

個人拙見,其實和JS阻塞DOM解析是出於相同的考慮——爲了不衝突。JS阻塞DOM解析的緣由相信各位都知道,而JS代碼不只能夠操做DOM節點,也能夠操做CSSOM上的節點,所以也應該不能同時進行,要等到CSS加載完成。一樣的,JS執行也會阻止CSSOM的解析。可能有人會有點繞不明白,DOM解析和JS互斥執行時是JS阻塞了DOM解析,爲何CSSOM這裏確是CSS阻塞了JS?個人理解HTML解析是至上而下的,其生效順序也是至上而下、後者覆蓋前者,而不是下載完成順序,所以要等被阻塞JS的前面的CSS解析完才能執行,後面的就沒必要管了。

四、小結

1)<head>裏

  •  js阻塞DOM解析
  • css不阻塞DOM解析,但阻塞頁面渲染和js的執行
2)<body>裏<link> <script>會觸發頁面渲染, 於是若是前面CSS資源(head裏)還沒有加載完成時,瀏覽器會等待它加載完畢再執行腳本

3)其餘(一些學來的知識點,只有結論,沒作實驗):

  • 圖像是不會阻塞頁面的首次渲染
  •  資源的下載一開始就開始 
  • 瀏覽器在遇到<body>標籤以前不會渲染頁面的任何部分

關鍵渲染路徑

關鍵渲染路徑及其優化是個比較複雜的問題,感興趣的朋友們能夠點這裏

  • 關鍵資源:可能阻塞網頁首次渲染的資源;
  • 關鍵路徑長度:獲取全部關鍵資源所需的往返次數或總時間;
  • 關鍵字節:實現網頁首次渲染所需的總字節數,是全部關鍵資源傳送文件大小的總和。

舉個簡單的HTML+1個JS文件+1個CSS文件的例子以下:


上圖關鍵路徑特性爲:

  • 3項關鍵資源
  • 2次或更屢次往返的最短關鍵路徑長度
  • 11KB的關鍵字節
想要儘早進行頁面首次渲染,提高用戶體驗,實際上就是對關鍵資源、關鍵路徑、關鍵字節的優化。

優化建議

研究了這麼多,固然是但願可以加快首屏加載速度,這裏從減小阻塞和優化關鍵三要素的角度提出一些優化建議。

一、CSS的優化建議

  • 將CSS置於文檔head標籤內
  • 【優化關鍵資源】經過CSS「媒體類型」和「媒體查詢」來標記某些CSS爲非阻塞資源。e.g.
    <link href="print.css" rel="stylesheet" media="print">
    <link href="other.css" rel="stylesheet" media="(min-width: 40em)">複製代碼
  • 【優化關鍵路徑】避免使用CSS import 
  • 【優化關鍵字節】Minify CSS文件 

 二、JavaScript的優化建議

  • 【優化阻塞】把腳本放在頁面尾部 </body> 以前的位置
  • 【優化阻塞】使用async或defer指令來避免阻塞渲染 
  • 【優化阻塞】避免運行時間長的JavaScript,可考慮拆分,以便瀏覽器間隔處理其餘事件
  • 【優化關鍵路徑】減小JavaScript文件嵌套調用
  • 【優化關鍵字節】Minify JavaScript文件

三、async和defer

  • async:下載完成後執行,執行順序亂序。可是當下載完成的時刻,渲染又會阻塞了,這是由於腳本執行了,當腳本執行完畢,渲染恢復。
  • defer:全部元素解析完成後,DOMContentLoaded前,按加載順序執行,支持內聯腳本。
  • DOMContentLoaded 是指在 dom 樹構建完畢以後觸發的事件, 而 onload 是 dom 樹構建以及所有依賴資源(含圖片)都下載完畢以後觸發。

四、''link'預加載

功能很強大的樣子,推薦你們看這裏,已經寫得很詳細了。

參考文獻

《<link>預加載功能詳解》

《渲染樹構建、佈局及繪製》

《爲何說DOM操做很慢》

《JS和CSS的位置對資源加載順序的影響》

《CSS加載會形成阻塞嗎?》

《原來CSS與JS是這樣阻塞DOM解析和渲染的》

《一個微信面試題引起的血案——[譯]什麼阻塞了DOM》

相關文章
相關標籤/搜索