浏览器是如何解析html的?
當我們在瀏覽器地址欄輸入一個合法的url時,瀏覽器首先進行DNS域名解析,拿到服務器IP地址后,瀏覽器給服務器發送GET請求,等到服務器正常返回后瀏覽器開始下載并解析html。這里僅總結瀏覽器解析html的過程。
html頁面主要由dom、css、javascript等部分構成,其中css和javascript既能內聯也能以腳本的形式引入,當然html中還可能引入img、iframe等其他資源。其實所有的這些資源也是以dom標簽的形式嵌入在html頁面中的,因此本篇總結說的html解析過程就是dom的解析過程。
1 dom解析過程
整個dom的解析過程是順序,并且漸進式的。
順序指的是從第一行開始,一行一行依次解析;漸進式則指得是瀏覽器會迫不及待的將解析完成的部分顯示出來,如果我們做下面這個實驗會發現,在斷點處第一個div已經在瀏覽器渲染出來了:
<html> <head> </head> <body><div>first div</div><script>debugger</script><div>second div</div> </body> </html> 復制代碼既然dom是從第一行按順序解析,那么我們怎么判斷dom何時解析完成呢?這個問題應該經常會在面試中問到,比如一般會問:
window.onload和DOMContentLoaded有什么區別?
其實就是想看看是不是明白dom樹何時構建完成,這個問題確實很重要,尤其是對于幾年前的jquery技術棧來說,因為我們使用javascript操作dom或者給dom綁定事件有個前提條件就是需要dom樹已經創建完成。整個html頁面的dom解析完成時,dom樹也就構建完成了。dom樹構建完成后document對象會派發事件DOMContentLoaded來通知dom樹已構建完成。
html從第一行開始解析,遇到外聯資源(外聯css、外聯javascript、image、iframe等)就會請求對應資源,那么請求過程是否會阻塞dom的解析過程呢?答案是看情況,有的資源會,有的資源不會。下面按是否會阻塞頁面解析分為兩類:阻塞型與非阻塞型,注意這里區分兩類資源的標志是document對象派發DOMContentLoaded事件的時間點,認為派發DOMContentLoaded事件才表示dom樹構建完成。
1.1 阻塞型
會阻塞dom解析的資源主要包括:
- 內聯css
- 內聯javascript
- 外聯普通javascript
- 外聯defer javascript
- javascript標簽之前的外聯css
外聯javascript可以用async與defer標示,因此這里分為了三類:外聯普通javascript,外聯defer javascript、外聯async javascript,這幾類外聯javascript本篇后面有詳細介紹。 dom解析過程中遇到外聯普通javascript會暫停解析,請求拿到javascript并執行,然后繼續解析dom樹。
對于外聯defer javascript這里重點說明下為什么也歸于阻塞型。前面也說了,這里以document對象派發DOMContentLoaded事件來標識dom樹構建完成,而defer javascript是在該事件派發之前請求并執行的,因此也歸類于阻塞型,但是需要知道,defer的javascript實際上是在dom樹構建完成與派發DOMContentLoaded事件之間請求并執行的,不過如果換個思路理解,<script>本身也是dom的一部分也就不難理解為什么defer的javascript會在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樹的解析的。外聯css對dom樹解析過程的影響這里有一篇非常好的文章介紹:DOMContentLoaded and stylesheets,推薦閱讀。
DOMContentLoaded事件用來標識dom樹構建完成,那如何判斷另外這些非阻塞型的資源加載完成呢?答案是window.onload。由于該事件派發的過晚,因此一般情況下我們用不著,而更多的是用DOMContentLoaded來盡早的的操作dom。
另外還有image、iframe以及外聯async javascript也不會阻塞dom樹的構建。這里外聯async javascript又是什么呢?下一節整體介紹下外聯javascript。
2 外聯javascript加載過程
html頁面中可以引入內聯javascript,也可以引入外聯javascript,外聯javascript又分為:
- 外聯普通javascript
- 外聯defer javascript
- 外聯async javascript
其中第一種就是外聯普通javascript,會阻塞html的解析,html解析過程中每遇到這種<script>標簽就會請求并執行,如下圖所示,綠色表示html解析;灰色表示html解析暫停;藍色表示外聯javascript加載;粉色表示javascript執行。
是外聯普通javascript的加載執行過程如下: 第二種外聯defer javascript稍有不同,html解析過程中遇到此類<script>標簽不阻塞解析,而是會暫存到一個隊列中,等整個html解析完成后再按隊列的順序請求并執行javascript,但是這種外聯defer javascript全部加載并執行完成后才會派發DOMContentLoaded事件,外聯defer javascript的加載執行過程如下: 第三種外聯async javascript則不阻塞html的解析過程,注意這里是說的腳本的下載過程不阻塞html解析,如果下載完成后html還沒解析完成,則會暫停html解析,先執行完成下載后的javascript代碼再繼續解析html,過程如下: 但是如果html已經解析完畢,外聯async javascript還未下載完成,則不阻塞DOMContentLoaded事件的派發。因此外聯async javascript很有可能來不及監聽DOMContentLoaded事件,比如stackoverflow上的這個問題。說明下,這幾個圖引用自這里。
3 DOMContentLoaded兼容性問題
DOMContentLoaded最開始由firefox提出,其他瀏覽器覺得非常有用也相繼開始支持,但是特性卻稍有不同,比如opera中javascript的執行并不等待外聯css的加載。直到HTML5出來后將DOMContentLoaded標準化,依照HTML5標準,javascript腳本執行前,出現在當前<script>之前的<link rel="stylesheet">必須完全載入。
那么在所有瀏覽器標準化之前怎么解決DOMContentLoaded的兼容性問題呢?可以參考jQuery中.ready()方法的實現,對于該方法的源碼分析網上已經一大堆了,這里就不做分析了,直接說下原理。其實是就是用了MDN: DOMContentLoaded中介紹的兼容性方法,ie9才開始支持DOMContentedLoaded,ie8環境可以通過檢測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.readystate為interactive或completed的狀態,因為外聯async javascript是不阻塞dom解析的,因此為了完全覆蓋前面的4種情況,需要監聽document.readystate的變化:
if (document.readystate === 'interactive'|| document.readystate === 'complete') {// 調用ready回調函數 } else {document.onreadystatechange = function () {if (document.readystate === 'interative') {// 調用ready回調函數}} } 復制代碼4 引用
主要參考了以下文章,推薦閱讀:
總結
以上是生活随笔為你收集整理的浏览器是如何解析html的?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Vue 中的compile操作方式
- 下一篇: 互联网1分钟 | 0410 腾讯QQ上线