1. 程式人生 > >通過chrome偵錯程式測試瞭解瀏覽器解析和渲染HTML的過程

通過chrome偵錯程式測試瞭解瀏覽器解析和渲染HTML的過程

1.基礎知識:瞭解chrome的Timeline工具

僅僅是通過理論知識,很難記住和理解瀏覽器解析html的原則,因此我動手做了些小實驗。而做這個實驗,不得不用到一個工具:chrome的Timeline工具。

這個工具真的很強大,Timeline工具欄提供了對於在裝載Web應用的過程中,時間花費情況的概覽,這些應用包括處理DOM事件, 頁面佈局渲染或者向螢幕繪製元素。Timeline可以通過事件,框架,和實時記憶體用量3個方面的資料來監測網頁,通過這些資料,我們可以方便的找出頁面中存在問題的地方。
這裡寫圖片描述

具體的使用方式,這篇部落格裡有詳盡的說明。

2.主要過程

  • 解析HTML
  • 構建DOM樹
  • DOM樹與CSS樣式進行附著構造呈現樹
  • 佈局
  • 繪製

3.解析與構建DOM樹

瀏覽器有專門的html解析器,並且是邊解析邊構建do’m樹的,因此將前兩部分放在一塊講。
總體的解析原則是:1.自上而下順序解析。2.解析過程中遇到外部樣式(link,style)和外部指令碼(script),會阻塞瀏覽器的解析。3.外部樣式和外部指令碼(在沒有async、defer屬性下)會並行載入,但是外部樣式會阻塞外部指令碼的執行。

即:html解析->外部樣式、指令碼載入->外部樣式執行->外部指令碼執行->html繼續解析
情況一:如果是動態指令碼(即內聯指令碼)則不受樣式影響,在解析到它時會執行。
情況二:如果是動態建立的樣式檔案,則不會阻塞後續任何型別指令碼的執行。
情況三:外部樣式後續

外部指令碼含有async屬性(IE下為defer),外部樣式不會阻塞該指令碼的載入與執行

3.1外部樣式、指令碼並行載入,外部樣式會阻塞後續指令碼執行,直到外部樣式載入並解析完畢。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
  <script>var start = +new Date;</script>
  <link href="http://udacity-crp.herokuapp.com/style.css?rtt=2"
rel="stylesheet">
<link href="http://udacity-crp.herokuapp.com/style.css?rtt=1" rel="stylesheet"> </head> <body> <p>I am here!</p> <span id="result"></span> <p>I am here two!</p> <script> var end = +new Date; document.getElementById('result').innerHTML = (end-start); </script> </body> </html>

程式碼執行結果:
這裡寫圖片描述
並行載入:
這裡寫圖片描述
阻塞執行:
這裡寫圖片描述
可以看到,外部css載入並解析(parse)完畢之後才執行後面的js程式碼。

注意
dom樹構建完畢會觸發DOMContentLoaded事件,但此時外部檔案不一定載入完畢,比如一些圖片。當頁面載入完畢後才出發load事件,這也是 jquery的$ (document).ready(function(){})和原生程式碼window.onload=function(){}的區別。(其他區別: window.onload不能同時編寫多個,如果有多個window.onload方法,只會執行一個; $(document).ready()可以同時編寫多個,並且都可以得到執行 )

3.2 外部樣式不會阻塞後續指令碼的載入,但會阻塞後續指令碼的執行

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
  <script>var start = +new Date;</script>
  <link href="http://udacity-crp.herokuapp.com/style.css?rtt=2" rel="stylesheet">

</head>

<body>
  test
  <script src="http://udacity-crp.herokuapp.com/time.js?rtt=1&a"></script>
  <div id="result"></div>
  <script>var end = +new Date;document.getElementById("result").innerHTML = end-start;</script>

</body>
</html>

外部指令碼程式碼:

var loadTime = document.createElement('div');
loadTime.innerText = document.currentScript.src + ' executed @ ' + window.performance.now();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);

執行結果:
這裡寫圖片描述
在瀑布流中檢視外部檔案載入順序:
這裡寫圖片描述
可以看到,確實是按先後順序發起請求,但是並行載入。
這裡寫圖片描述
可以看你到,在外部樣式載入和解析完畢,外部指令碼才開始執行

3.3 如果後續外部指令碼含有async屬性,則該指令碼不會被樣式檔案阻塞

這裡我直接在3.2 中外部指令碼標籤中加入async ,程式碼就不貼了,看下執行結果:
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
可以看到 外部引入的time.js檔案在不沒有被css阻塞。

3.4 動態建立的樣式檔案不會阻塞其後的script的執行,不管script標籤是否具有async屬性

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
  <script>var start = +new Date;</script>

</head>

<body>
  test
  <script> 
    var link = document.createElement('link');
    link.href = "http://udacity-crp.herokuapp.com/style.css?rtt=2";
    link.rel = "stylesheet";
    document.head.appendChild(link);
  </script>
  <div id="result"></div>
  <script>var end = +new Date;document.getElementById("result").innerHTML = end-start;</script>

</body>
</html>

執行結果:
這裡寫圖片描述
動態建立的link載入了3秒左右,而第一個script指令碼(line:6)在67ms時執行,第三個script指令碼(line:19)在75ms左右執行。
這裡寫圖片描述
這裡寫圖片描述

3.5動態建立的樣式檔案不會阻塞其後動態建立的script的執行

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
  <script>var start = +new Date;</script>

</head>

<body>
  test
  <script> 
    var link = document.createElement('link');
    link.href = "http://udacity-crp.herokuapp.com/style.css?rtt=2";
    link.rel = "stylesheet";
    document.head.appendChild(link);
    var script = document.createElement('script');
    script.src = "http://udacity-crp.herokuapp.com/time.js?rtt=1&a";
    document.head.appendChild(script);
  </script>
  <div id="result"></div>
  <script>var end = +new Date;document.getElementById("result").innerHTML = end-start;</script>

</body>
</html>

這裡寫圖片描述
可以看到,動態指令碼在3秒左右才載入完畢,後其後的指令碼在1.8秒左右就以及載入並執行了。另外第三個script標籤在2ms左右就執行了,可見,js指令碼無論是動態建立還是直接引入在頁面的,順序載入,但並行執行。這個在3.6節進行進一步證明。

3.6動態建立的指令碼的載入和執行均會被之前的樣式檔案阻塞,但不會阻塞後續指令碼的執行

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
  <script>var start = +new Date;</script>
  <link href="http://udacity-crp.herokuapp.com/style.css?rtt=2" rel="stylesheet">
</head>

<body>
  test
  <script> 
    var script = document.createElement('script');
    script.src = "http://udacity-crp.herokuapp.com/time.js?rtt=1&a";
    document.head.appendChild(script);
  </script>
  </script>
  <div id="result"></div>
  <script>var end = +new Date;document.getElementById("result").innerHTML = end-start;</script>

</body>
</html>

這裡寫圖片描述
這裡寫圖片描述
可以看到,動態建立的指令碼並沒有阻塞後面指令碼的執行,而且它的執行方式和其他指令碼一樣,均會被之前的CSS阻塞

4 構建渲染樹,繪製與佈局

在外部樣式執行完畢後,css附著於DOM,建立了一個渲染樹(渲染樹是一些被渲染物件的集),也就是說渲染樹與dom樹是同時構建的。每個渲染物件都包含了與之對應的計算過樣式的DOM物件,對於每個渲染元素來說,位置都經過計算,所以這裡被叫做“佈局”。然後將“佈局”顯示在瀏覽器視窗,稱之為“繪製”。
同指令碼檔案一樣,CSS樣式表會阻塞圖片的載入,而指令碼檔案不會。接著指令碼的執行完畢後,DOM樹構建完成。

渲染樹的節點(渲染器),在Gecko中稱為frame,而在webkit中稱為renderer。渲染器是在文件解析和建立DOM節點後建立的,會計算DOM節點的樣式資訊。

在webkit中,renderer是由DOM節點呼叫attach()方法建立的。attach()方法計算了DOM節點的樣式資訊。attach()是自上而下的遞迴操作。也就是說,父節點總是比子節點先建立自己的renderer。銷燬的時候,則是自下而上的遞迴操作,也就是說,子節點總是比父節點先銷燬。

如果元素的display屬性被設定成了none,或者如果元素的子孫繼承了display:none,renderer不會被建立。節點的子類和display屬性一起決定為該節點建立什麼樣的渲染器。但是visibility:hidden的元素會被建立

具體如何構建渲染樹,可參見: