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