當咱們在瀏覽器地址欄輸入一個合法的url
時,瀏覽器首先進行DNS
域名解析,拿到服務器IP地址後,瀏覽器給服務器發送GET
請求,等到服務器正常返回後瀏覽器開始下載並解析html
。這裏僅總結瀏覽器解析html的過程。javascript
html
頁面主要由dom
、css
、javascript
等部分構成,其中css
和javascript
既能內聯
也能以腳本
的形式引入,固然html
中還可能引入img
、iframe
等其餘資源。其實全部的這些資源也是以dom
標籤的形式嵌入在html
頁面中的,所以本篇總結說的html
解析過程就是dom
的解析過程。css
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.onload
和DOMContentLoaded
有什麼區別?web
其實就是想看看是否是明白dom樹
什麼時候構建完成,這個問題確實很重要,尤爲是對於幾年前的jquery
技術棧來講,由於咱們使用javascript
操做dom
或者給dom
綁定事件有個前提條件就是須要dom樹
已經建立完成。整個html
頁面的dom
解析完成時,dom樹
也就構建完成了。dom樹構建完成後document
對象會派發事件DOMContentLoaded
來通知dom樹
已構建完成。面試
html
從第一行開始解析,遇到外聯
資源(外聯css
、外聯javascript
、image
、iframe
等)就會請求對應資源,那麼請求過程是否會阻塞dom
的解析過程呢?答案是看狀況,有的資源會,有的資源不會。下面按是否會阻塞頁面解析分爲兩類:阻塞型
與非阻塞型
,注意這裏區分兩類資源的標誌是document
對象派發DOMContentLoaded
事件的時間點,認爲派發DOMContentLoaded
事件才表示dom樹
構建完成。瀏覽器
會阻塞dom
解析的資源主要包括:服務器
外聯javascript
能夠用async
與defer
標示,所以這裏分爲了三類:外聯普通javascript
,外聯defer javascript
、外聯async javascript
,這幾類外聯javascript
本篇後面有詳細介紹。 dom
解析過程當中遇到外聯普通javascript
會暫停解析,請求拿到javascript
並執行,而後繼續解析dom樹
。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 forstylesheets
, 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樹
構建完成。
不阻塞dom
解析的資源主要包括:
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
。
外聯javascript
加載過程html
頁面中能夠引入內聯javascript
,也能夠引入外聯javascript
,外聯javascript
又分爲:
<script src="indx.js"></script>
複製代碼
<script defer src="indx.js"></script>
複製代碼
<script async src="indx.js"></script>
複製代碼
其中第一種就是外聯普通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
上的
這個問題。
說明下,這幾個圖引用自這裏。
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種狀態:
DOMContentLoaded
window onload
所以咱們經過判斷document.readystate
的狀態爲interactive
來模擬DOMContentLoaded
時間點。可是這裏須要注意一點,以.ready()
方法爲例,咱們可能在下面這幾個地方調用:
其中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回調函數
}
}
}
複製代碼
主要參考瞭如下文章,推薦閱讀: