瀏覽器是如何解析html的?

當咱們在瀏覽器地址欄輸入一個合法的url時,瀏覽器首先進行DNS域名解析,拿到服務器IP地址後,瀏覽器給服務器發送GET請求,等到服務器正常返回後瀏覽器開始下載並解析html。這裏僅總結瀏覽器解析html的過程。javascript

html頁面主要由domcssjavascript等部分構成,其中cssjavascript既能內聯也能以腳本的形式引入,固然html中還可能引入imgiframe等其餘資源。其實全部的這些資源也是以dom標籤的形式嵌入在html頁面中的,所以本篇總結說的html解析過程就是dom的解析過程。css

1 dom解析過程

整個dom的解析過程是順序,而且漸進式的。html

順序指的是從第一行開始,一行一行依次解析;漸進式則指得是瀏覽器會火燒眉毛的將解析完成的部分顯示出來,若是咱們作下面這個實驗會發現,在斷點處第一個div已經在瀏覽器渲染出來了:java

<!DOCTYPE html>
<html>
<head>
</head>
<body>
    <div>
        first div
    </div>
    <script> debugger </script>
    <div>
        second div
    </div>
</body>
</html>
複製代碼

既然dom是從第一行按順序解析,那麼咱們怎麼判斷dom什麼時候解析完成呢?這個問題應該常常會在面試中問到,好比通常會問:jquery

window.onloadDOMContentLoaded有什麼區別?web

其實就是想看看是否是明白dom樹什麼時候構建完成,這個問題確實很重要,尤爲是對於幾年前的jquery技術棧來講,由於咱們使用javascript操做dom或者給dom綁定事件有個前提條件就是須要dom樹已經建立完成。整個html頁面的dom解析完成時,dom樹也就構建完成了。dom樹構建完成後document對象會派發事件DOMContentLoaded來通知dom樹已構建完成。面試

html從第一行開始解析,遇到外聯資源(外聯css外聯javascriptimageiframe等)就會請求對應資源,那麼請求過程是否會阻塞dom的解析過程呢?答案是看狀況,有的資源會,有的資源不會。下面按是否會阻塞頁面解析分爲兩類:阻塞型非阻塞型,注意這裏區分兩類資源的標誌是document對象派發DOMContentLoaded事件的時間點,認爲派發DOMContentLoaded事件才表示dom樹構建完成。瀏覽器

1.1 阻塞型

會阻塞dom解析的資源主要包括:服務器

  • 內聯css
  • 內聯javascript
  • 外聯普通javascript
  • 外聯defer javascript
  • javascript標籤以前的外聯css

外聯javascript能夠用asyncdefer標示,所以這裏分爲了三類:外聯普通javascript外聯defer javascript外聯async javascript,這幾類外聯javascript本篇後面有詳細介紹。 dom解析過程當中遇到外聯普通javascript會暫停解析,請求拿到javascript並執行,而後繼續解析dom樹dom

對於外聯defer javascript這裏重點說明下爲何也歸於阻塞型。前面也說了,這裏以document對象派發DOMContentLoaded事件來標識dom樹構建完成,而defer javascript是在該事件派發以前請求並執行的,所以也歸類於阻塞型,可是須要知道,deferjavascript其實是在dom樹構建完成與派發DOMContentLoaded事件之間請求並執行的,不過若是換個思路理解,<script>自己也是dom的一部分也就不難理解爲何deferjavascript會在DOMContentLoaded派發以前執行了。

另外須要注意的是javascript標籤以前的外聯css。其實按說css資源是不該該阻塞dom樹的構建過程的,畢竟css隻影響dom樣式,不影響dom結構,MDN上也是這麼解釋的:

The DOMContentLoaded event is fired when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading.

可是實際狀況是dom樹的構建受javascript的阻塞,而javascript執行時又可能會使用相似Window.getComputedStyle()之類的API來獲取dom樣式,好比:

const para = document.querySelector('p');
const compStyles = window.getComputedStyle(para);
複製代碼

所以瀏覽器通常會在遇到<script>標籤時將該標籤以前的外聯css請求並執行完成。可是注意這裏加了一個前提條件就是javascript標籤以前的外聯css,就是表示被javascript執行依賴的外聯css。這個容易忽略的點這篇文章也有說明,推薦閱讀。

這些阻塞型的資源請求並執行完以後dom樹的解析便完成了,這時document對象就會派發DOMContentLoaded事件,表示dom樹構建完成。

1.2 非阻塞型

不阻塞dom解析的資源主要包括:

  • javascript標籤以後的外聯css
  • image
  • iframe
  • 外聯async javascript

dom樹解析完成以後會派發DOMContentLoaded事件,對於外聯css資源來講分爲兩類,一類是位於<script>標籤以前,一類是位於<script>標籤以後。位於<script>標籤以後的外聯css是不阻塞dom樹的解析的。外聯cssdom樹解析過程的影響這裏有一篇很是好的文章介紹:DOMContentLoaded and stylesheets,推薦閱讀。

DOMContentLoaded事件用來標識dom樹構建完成,那如何判斷另外這些非阻塞型的資源加載完成呢?答案是window.onload。因爲該事件派發的過晚,所以通常狀況下咱們用不着,而更多的是用DOMContentLoaded來儘早的的操做dom

另外還有imageiframe以及外聯async javascript也不會阻塞dom樹的構建。這裏外聯async javascript又是什麼呢?下一節總體介紹下外聯javascript

2 外聯javascript加載過程

html頁面中能夠引入內聯javascript,也能夠引入外聯javascript外聯javascript又分爲:

  • 外聯普通javascript
<script src="indx.js"></script>
複製代碼
  • 外聯defer javascript
<script defer src="indx.js"></script>
複製代碼
  • 外聯async javascript
<script async src="indx.js"></script>
複製代碼

其中第一種就是外聯普通javascript,會阻塞html的解析,html解析過程當中每遇到這種<script>標籤就會請求並執行,以下圖所示,綠色表示html解析;灰色表示html解析暫停;藍色表示外聯javascript加載;粉色表示javascript執行

標記
外聯普通javascript的加載執行過程以下:
外聯普通javascript
第二種 外聯defer javascript稍有不一樣, html解析過程當中遇到此類 <script>標籤不阻塞解析,而是會暫存到一個隊列中,等整個 html解析完成後再按隊列的順序請求並執行 javascript,可是這種 外聯defer javascript所有加載並執行完成後纔會派發 DOMContentLoaded事件, 外聯defer javascript的加載執行過程以下:
外聯defer javascript
第三種 外聯async javascript則不阻塞 html的解析過程,注意這裏是說的腳本的 下載過程不阻塞 html解析,若是下載完成後 html還沒解析完成,則會暫停 html解析,先執行完成下載後的 javascript代碼再繼續解析 html,過程以下:
外聯async javascript
可是若是 html已經解析完畢, 外聯async javascript還未下載完成,則不阻塞 DOMContentLoaded事件的派發。所以 外聯async javascript頗有可能來不及監聽 DOMContentLoaded事件,好比 stackoverflow上的 這個問題

說明下,這幾個圖引用自這裏

3 DOMContentLoaded兼容性問題

DOMContentLoaded最開始由firefox提出,其餘瀏覽器以爲很是有用也相繼開始支持,可是特性卻稍有不一樣,好比operajavascript的執行並不等待外聯css的加載。直到HTML5出來後將DOMContentLoaded標準化,依照HTML5標準,javascript腳本執行前,出如今當前<script>以前的<link rel="stylesheet">必須徹底載入。

那麼在全部瀏覽器標準化以前怎麼解決DOMContentLoaded的兼容性問題呢?能夠參考jQuery.ready()方法的實現,對於該方法的源碼分析網上已經一大堆了,這裏就不作分析了,直接說下原理。實際上是就是用了MDN: DOMContentLoaded中介紹的兼容性方法,ie9纔開始支持DOMContentedLoadedie8環境能夠經過檢測document.readystate狀態來確認dom樹是否構建完成。document.readystate包括3種狀態:

  • loading - html文檔加載中
  • interactive - html文檔加載並解析完成,可是圖片等資源還未完成加載,至關於DOMContentLoaded
  • complete - 全部資源加載完成,至關於window onload

所以咱們經過判斷document.readystate的狀態爲interactive來模擬DOMContentLoaded時間點。可是這裏須要注意一點,以.ready()方法爲例,咱們可能在下面這幾個地方調用:

  • 內聯javasctipt
  • 外聯普通javascript
  • 外聯defer javascript
  • 外聯async javascript

其中3三個地方直接判斷document.readystate確定是loading狀態,只有外聯async javascript可能出現document.readystateinteractivecompleted的狀態,由於外聯async javascript是不阻塞dom解析的,所以爲了徹底覆蓋前面的4種狀況,須要監聽document.readystate的變化:

if (document.readystate === 'interactive'
    || document.readystate === 'complete') {
        // 調用ready回調函數
} else {
    document.onreadystatechange = function () {
        if (document.readystate === 'interative') {
            // 調用ready回調函數
        }
    } 
}
複製代碼

4 引用

主要參考瞭如下文章,推薦閱讀:

  1. Page lifecycle: DOMContentLoaded, load, beforeunload, unload
  2. DOMContentLoaded and stylesheets
  3. script標籤: async vs defer attributes
  4. MDN: DOMContentLoaded
  5. MDN: readystatechange
  6. Replace jQuery’s Ready() with Plain JavaScript
相關文章
相關標籤/搜索