咱們先了解兩個事件,有助於後面的分析。css
load事件:load 應該僅用於檢測一個徹底加載的頁面 當一個資源及其依賴資源已完成加載時,將觸發load事件。也就是說,頁面的html、css、js、圖片等資源都已經加載完以後纔會觸發 load 事件。html
DOMContentLoaded事件:當初始的 HTML 文檔被徹底加載和解析完成以後,DOMContentLoaded 事件被觸發,而無需等待樣式表、圖像和子框架的完成加載。也就是說,DOM 樹已經構建完畢就會觸發 DOMContentLoaded 事件。webpack
由於js在執行的過程當中可能會操做DOM,發生迴流和重繪,因此GUI渲染線程與JS引擎線程是互斥的。web
在解析HTML過程當中,若是遇到 script 標籤,渲染線程會暫停渲染過程,將控制權交給 JS 引擎。內聯的js代碼會直接執行,若是是js外部文件,則要下載該js文件,下載完成以後再執行。等 JS 引擎運行完畢,瀏覽器又會把控制權還給渲染線程,繼續 DOM 的解析。瀏覽器
所以,js會阻塞DOM樹的構建。markdown
那麼,是否會阻塞頁面的顯示呢?咱們用下面的代碼來測試一下。框架
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>hello world</div>
<script>
debugger
</script>
<div>hello world2</div>
</body>
</html>
複製代碼
能夠看到,這個頁面的DOMContentLoaded發生在2.23s,可見js阻塞了DOM樹的構建。可是,頁面上卻幾乎在一瞬間顯示了hello world
,說明js不會阻塞位於它以前的dom元素的渲染。dom
現代瀏覽器爲了更好的用戶體驗,渲染引擎將嘗試儘快在屏幕上顯示的內容。它不會等到全部DOM解析完成後才佈局渲染樹。而是當js阻塞發生時,會將已經構建好的DOM元素渲染到屏幕上,減小白屏的時間。異步
這也是爲何咱們會將script標籤放到body標籤的底部,由於這樣就不會影響前面的頁面的渲染。async
當咱們解析 HTML 時遇到 link 標籤或者 style 標籤時,就會計算樣式,構建CSSOM。
css不會阻塞dom樹的構建,可是會阻塞頁面的顯示。咱們依然用一個例子來測試:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" type="text/css" href="https://h5.sinaimg.cn/m/weibo-pro/css/chunk-vendors.d6cac585.css">
</head>
<body>
<div class="woo-spinner-filled">hello world</div>
<div>hello world2</div>
</body>
</html>
複製代碼
使用一個外部css文件,打開Slow 3G
模擬比較慢的網速,能夠看到,DOMContentLoaded事件觸發只用了30ms,頁面此時依然是空白,而幾乎是loaded事件2.92s發生時,頁面纔出現內容。
緣由是,瀏覽器在構建 CSSOM 的過程當中,不會渲染任何已處理的內容。即使 DOM 已經解析完畢了,只要 CSSOM 不沒構建好,頁面也不會顯示內容。
只有當咱們遇到 link 標籤或者 style 標籤時,纔會構建CSSOM,因此若是 link 標籤以前有dom元素,當加載css發生阻塞時,瀏覽器會將前面已經構建好的DOM元素渲染到屏幕上,以減小白屏的時間。好比下面這樣:
<body>
<div class="woo-spinner-filled">hello world</div>
<link rel="stylesheet" type="text/css" href="https://h5.sinaimg.cn/m/weibo-pro/css/chunk-vendors.d6cac585.css">
<div>hello world2</div>
</body>
複製代碼
這樣作會致使一個問題,就是頁面閃爍,在css被加載以前,瀏覽器按照默認樣式渲染 <div class="woo-spinner-filled">hello world</div>
,當css加載完成,會爲該div計算新的樣式,從新渲染,出現閃爍的效果。
爲了不頁面閃爍,一般 link 標籤都放在head中。
css會不會阻塞後面js執行?答案是會!
JS 的做用在於修改,它幫助咱們修改網頁的方方面面:內容、樣式以及它如何響應用戶交互。這「方方面面」的修改,本質上都是對 DOM 和 CSSDOM 進行修改。當在JS中訪問了CSSDOM中某個元素的樣式,那麼這時候就須要等待這個樣式被下載完成才能繼續往下執行JS腳本。
運行下面這個例子,就會發現等css加載完成後,纔會在控制檯打印「this is a test」。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" type="text/css" href="https://h5.sinaimg.cn/m/weibo-pro/css/chunk-vendors.d6cac585.css">
</head>
<body>
<div class="woo-spinner-filled">hello world</div>
<div>hello world2</div>
<script>
console.log('this is a test')
</script>
</body>
</html>
複製代碼
使用內聯 JavaScript 和 CSS,這樣獲取到 HTML 文件以後就能夠直接開始渲染流程了。
並非全部的場合都適合內聯,那麼還能夠儘可能減小文件大小,好比經過 webpack 等構建工具刪除無用代碼、壓縮 css、JavaScript 文件的體積;而且啓用 CDN 加快文件的下載速度。
對於大的 CSS 文件,能夠經過媒體查詢屬性,將其拆分爲多個不一樣用途的 CSS 文件,這樣只有在特定的場景下才會加載特定的 CSS 文件。
若是 JavaScript 文件中沒有操做 DOM 相關代碼,就能夠將該 JavaScript 腳本設置爲異步加載,經過 async 或 defer 來標記代碼。
<script src="index.js"></script>
//瀏覽器必須等待 index.js 加載和執行完畢才能去作其它事情。
<script async src="index.js"></script>
//index.js 的加載是異步的,加載時不會阻塞瀏覽器作任何其它的事情。
//當它加載結束,JS 腳本會當即執行。
<script defer src="index.js"></script>
//JS 的加載是異步的,執行是被推遲的。
//使用了 defer 標記的腳本文件,會等整個文檔解析完成,在 DOMContentLoaded 事件觸發以前執行
複製代碼