DOM, CSS, JS的阻塞,解析渲染順序

DOMContentLoaded

當初始的 HTML 文檔被徹底加載和解析完成以後,DOMContentLoaded 事件被觸發,而無需等待樣式表、圖像和子框架的完成加載。另外一個不一樣的事件 load 應該僅用於檢測一個徹底加載的頁面。 在使用 DOMContentLoaded 更加合適的狀況下使用 load 是一個使人難以置信的流行的錯誤,因此要謹慎。注意:DOMContentLoaded 事件必須等待script以前的樣式表加載解析完成纔會觸發。javascript

// 客戶端 html 代碼
<head>
	<meta charset="UTF-8">
	<title>Title</title>
	<script>
		document.addEventListener('DOMContentLoaded',function(){
			console.log('3 seconds passed');
		});
	</script>
	<link rel="stylesheet" href="http://localhost:3000/sleep">
</head>
<body>
<div><a >1234567</a></div>
</body>

// express 代碼
var express = require('express');
var app = express();

app.get('/sleep', function(req, res, next) {
	res.type('text/css')
	setTimeout(()=>{
		res.send("a {\n" +
			" color: red;\n" +
			"}")

	},3000)
});
複製代碼

當script在上,link在下時:

控制檯當即顯示 3 seconds passed。 頁面是空白的。 過三秒後,頁面直接顯示紅字。css

當link在上,script在下時:

頁面是空白的。過三秒後,html

控制檯顯示 3 seconds passed。java

頁面直接顯示紅字。express

說明:css link會阻塞 DOM 渲染

  • css link會阻塞 DOM 渲染,當css加載完成後纔會渲染。
  • DOMContentLoaded 事件只等待script以前的樣式表
  • 由於:CSS 會阻塞 JS 執行,JS 會阻塞 DOM解析,下面會代碼說明

由於 DOM 的渲染須要 DOM 樹和 CSSOM 樹共同來生成渲染樹,因此在 CSS 加載完成以前, DOM 是不會進行渲染的。仍是用上面那個例子,咱們會發現 CSS 沒有加載完成時,頁面上是不顯示的,而當 CSS 加載完成的瞬間,標籤就被渲染到頁面上了。瀏覽器

當把css link放在body內,而不是head內又不一樣

像截圖這種代碼,link也在script標籤後面。

結果:頁面先顯示出div內容,過三秒後,字體顏色改變+控制檯打印。緩存

CSS 不會阻塞 DOM 解析

// html內的代碼
<head>
	<meta charset="UTF-8">
	<title>Title</title>
	<link rel="stylesheet" href="http://localhost:3000/sleep">
	<script defer src="js/test.js"></script>

</head>
<body>
<div><a >1234567</a></div>
</body>


//  js/test.js
const div = document.querySelector('div');
console.log(div);
複製代碼

此時咱們的 css 還沒加載完成,可是咱們能夠看到 console 選項卡里面 div 標籤已經被打印出來了,說明,此時 DOM 已經解析完成觸發 DOMContentLoaded 事件。bash

CSS 會阻塞 JS 執行

<script>
	var starttime = new Date().getTime();
	console.log("page start" + starttime);
</script>
<link rel="stylesheet" href="http://localhost:3000/sleep">
<script src="js/test.js"></script>

//  js/test.js
var endtime = new Date().getTime();
console.log("delay:" + (endtime - starttime));
複製代碼

sleep 3秒後才返回 網絡

JS 會阻塞 DOM解析

當解析起遇到 script 標籤時,文檔會當即中止解析直到腳本執行完畢,若是腳本是外部的,那麼解析過程會中止,直到從網絡同步抓取資源完成後再繼續。由於咱們的腳本會操做 DOM,因此在腳本跑完以前瀏覽器不知道腳本會把 DOM 改爲什麼樣,因此就等腳本執行完再進行解析。app

<script >
	let ui = document.getElementById('ui')
	ui.innerText = 'blue'
</script>
<div id="ui">test3</div>
複製代碼

從代碼和圖中看到,當把dom放在js下面,取不到元素,報錯。

script 標籤對 dom 渲染的影響

GUI 渲染線程與 JS 引擎線程是互斥的,當 JS 引擎執行時 GUI 線程會被掛起(至關於被凍結了),GUI 更新會被保存在一個隊列中等到 JS 引擎空閒時當即被執行。

  • 當遇到script標籤爲同步的形式,會當即執行js,執行完再渲染。
<div id="ui">test3</div>
<script >

	var now = new Date().getTime();

	let ui = document.getElementById('ui')

	while (new Date().getTime() - now < 3000) {
		continue;
	}

	ui.innerText = 'blue'

</script>

複製代碼
  • 當遇到script標籤爲引用的形式,會當即把前面已經解析的部分渲染了。
<div id="ui">test3</div>

<script src="js/test.js"></script>

// js/test.js 內容與上面script內的內容同樣

複製代碼

另外一套測試代碼

// 引入遠程文件
<script src="http://localhost:3000/test.js"></script>

// express 服務端代碼,2秒後再返回
app.get('/test.js', function(req, res, next) {
	res.type('application/javascript')
	setTimeout(()=>{
		res.send(`alert()`)
	},2000)
});
複製代碼

總結

瀏覽器執行 css dom js 在同步模式下,都是從上往下執行。 渲染等到 JS 引擎空閒時當即被執行。

CSS 會阻塞 JS 執行

JS 會阻塞 DOM解析

CSS 不會阻塞 DOM 解析

Css 會阻塞 DOM 渲染

引用script 會渲染部分已經解析過的dom,

包含內的形式會先執行js,渲染掛起。

主要緣由仍是script內代碼有沒有加載完,若是沒有加載完就會先渲染一次,加載完就會先執行js,看js有沒有改變dom,避免浪費渲染一次,很智能。

比起js的懶加載, css懶加載和初始加載最小體積會更加提升性能。

defer async

async 模式下,JS 不會阻塞瀏覽器作任何其它的事情。它的加載是異步的,當它加載結束,JS 腳本會當即執行。

defer 模式下,JS 的加載是異步的,執行是被推遲的。等整個文檔解析完成、DOMContentLoaded 事件即將被觸發時,被標記了 defer 的 JS 文件纔會開始依次執行。

從應用的角度來講,通常當咱們的腳本與 DOM 元素和其它腳本之間的依賴關係不強時,咱們會選用 async;當腳本依賴於 DOM 元素和其它腳本的執行結果時,咱們會選用 defer。

一句話,defer是「解析完dom再執行」,async是「下載完就執行」。另外,若是有多個defer腳本,會按照它們在頁面出現的順序加載,而多個async腳本是不能保證加載順序的。

preload prefetch

preload

<link>元素的 rel 屬性的屬性值preload可以讓你在你的HTML頁面中 <head>元素內部書寫一些聲明式的資源獲取請求,能夠指明哪些資源是在頁面加載完成後即刻須要的。對於這種即刻須要的資源,你可能但願在頁面加載的生命週期的早期階段就開始獲取,在瀏覽器的主渲染機制介入前就進行預加載。這一機制使得資源能夠更早的獲得加載並可用,且更不易阻塞頁面的初步渲染,進而提高性能。

  • 只是預加載,並不運行。
  • 用下面兩種方法便可加載完運行
// 預加載Css
<link rel="preload" as="style" href="async_style.css" onload="this.rel='stylesheet'">

// 預加載js
 <link rel="preload" as="script" href="async_script.js"
         onload="var script = document.createElement('script'); script.src = this.href; document.body.appendChild(script);">


複製代碼

參考 MDN

參考 preload

DEMO preload

Link_prefetching_FAQ

prefetch

瀏覽器會查找關係類型(rel)爲 next 或 prefetch 的 HTML 或 HTTP Link: header。例子:

<link rel="prefetch" href="/images/big.jpeg">

<link rel="prefetch alternate stylesheet" title="Designed for Mozilla" href="mozspecific.css">

<link rel="next" href="2.html">
複製代碼

已經被許多瀏覽器支持了至關長的時間,但它是意圖預獲取一些資源, 以備下一個導航/頁面使用(好比,當你去到下一個頁面時)。 這很好,但對當前的頁面並無什麼助益。 此外,瀏覽器會給使用prefetch的資源一個相對較低的優先級——與使用preload的資源相比。畢竟,當前的頁面比下一個頁面相對更加劇要。

preload > load > prefetch 優先級測試

<link  rel="stylesheet" href="http://localhost:3000/sleep2.load" >

<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload" >
<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload1" >
<link rel="prefetch" as="style" href="http://localhost:3000/sleep2.prefetch" >
<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload2" >
<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload3" >
<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload4" >
<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload5" >
<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload6" >
<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload7" >

<link  rel="stylesheet" href="http://localhost:3000/sleep.load" >
複製代碼

從下圖看出,preload比正常load優先級大,會優先佔用tcp連接,即便sleep2.load 寫在最上面,也要等6條tcp佔用釋放一條。prefetch 會在空閒時再使用。

  • preload > load > prefetch
  • preload 不會阻塞 DOMContentLoaded
  • preload 會讓請求在dom解析以前發出去,在真實請求的時候 會複用preload的請求緩存

相關文章
相關標籤/搜索