在探討 CSS、JS 對阻塞行爲前,先創建以下的 html,後續的探討都在這個 html 的基礎上進行。css
html 文件以下:html
<!DOCTYPE html>
<html lang="en">
<head>
<style>
div {
width: 100px;
height: 100px;
background: blue;
}
</style>
</head>
<body>
<div />
</body>
</html>
複製代碼
能夠預見的是 html 加載完畢後頁面會呈現一個藍色的正方形。git
demo 地址github
<script src="script.js"></script>
chrome
對於沒有 async 和 defer 屬性的 script,當瀏覽器解析到 script 標籤時會當即加載並執行腳本,這會阻止 dom 的解析,也就說在 script 加載執行完成前 script 標籤後的 dom 都不會解析。瀏覽器
加載腳本阻止 dom 解析bash
以下,head 內添加了一個內聯腳本,一個外部腳本(sleep.js,爲一個空文件),外部腳本將在服務端延遲 5 秒後返回。當 document.readyState 變爲 interactive 可交互時,代表文檔已解析完成,接近於 DOMContentLoaded 事件的觸發。服務器
<head>
<script>
console.log('start');
document.onreadystatechange = function () {
if (document.readyState === "interactive") {
console.log('DOMContentLoaded', document.body.children);
}
}
</script>
<script src="/sleep.js"></script>
</head>
複製代碼
效果以下: dom
能夠看到,頁面刷新,start 首先執行,5 秒後 DOMContentLoaded 才執行,也就是說 js 的加載會阻止 dom 的解析。事實上多數瀏覽器在 js 加載執行時都會中止解析文檔,由於 js 可能操做 dom。async
執行腳本阻止 dom 解析
以下,head 內添加了兩個內聯腳本,第二個內聯腳本將執行至少 5 秒鐘。
<head>
<script>
console.log('start');
document.onreadystatechange = function () {
if (document.readyState === "interactive") {
console.log('DOMContentLoaded', document.body.children);
}
}
</script>
<script>
var now = Date.now();
var isRun = true;
while(isRun) {
var time = Date.now();
if (time - now > 5000) {
isRun = false;
}
}
console.log('body', document.body);
console.log('end');
</script>
</head>
複製代碼
效果以下: 能夠看到,頁面刷新,start 首先執行,5 秒後 DOMContentLoaded 才執行,腳本執行完畢前 body 爲 null,也就是說 js 的執行會阻止 dom 的解析。
<script async src="script.js"></script>
async 屬性會使腳本後續文檔的加載渲染和腳本的加載執行並行進行。async 腳本在下載完成後當即執行,因此不能保證腳本的執行順序,以亂序執行爲主。此外,async 不支持內聯腳本。
加載 async 腳本不阻止 dom 解析
以下,將"加載腳本阻止 dom 解析"例子中的腳本改爲 async。
<script async src="/sleep.js"></script>
複製代碼
頁面刷新,能夠看到 DOMContentLoaded 當即打印了,也就說帶有 async 屬性的腳本加載時不會阻塞 dom 的解析。
執行 async 腳本不阻止 dom 解析
添加以下代碼到 sleep.js
var now = Date.now();
var isRun = true;
while(isRun) {
var time = Date.now();
if (time - now > 5000) {
isRun = false;
}
}
console.log('body', document.body);
console.log('end');
複製代碼
以下: 以 async 的方式加載 sleep.js,服務端當即返回 sleep.js。
<head>
<script>
console.log('start');
document.onreadystatechange = function () {
if (document.readyState === "interactive") {
console.log('DOMContentLoaded', document.body.children);
}
}
</script>
<script async src="/sleep.js"></script>
</head>
複製代碼
頁面刷新,能夠看到 DOMContentLoaded 當即打印了,也就說帶有 async 屬性的腳本執行時不會阻塞 dom 的解析。
<script defer src="script.js"></script>
defer 會使腳本後續文檔的加載渲染和腳本的加載並行進行,但 defer 腳本的執行要在全部元素解析完成以後 DOMContentLoaded 事件觸發前完成,它是按着腳本加載順序進行執行。
加載 defer 腳本不阻止 dom 解析
將 "加載 async 腳本不阻止 dom 解析例子" 中 async 換成 defer
<script defer src="/sleep.js"></script>
複製代碼
頁面刷新,能夠看到 DOMContentLoaded 當即打印了,也就說帶有 defer 屬性的腳本加載時不會阻塞 dom 的解析。
執行 defer 腳本不阻止 dom 解析
將 "執行 async 腳本不阻止 dom 解析例子" 中 async 換成 defer。
<script>
console.log('start');
document.onreadystatechange = function () {
if (document.readyState === "interactive") {
console.log('DOMContentLoaded', document.body.children);
}
}
</script>
<script defer src="/sleep.js"></script>
複製代碼
頁面刷新,能夠看到 DOMContentLoaded 當即打印了,也就說帶有 defer 屬性的腳本執行時不會阻塞 dom 的解析。
在 html 的 head 標籤內加上 script 標籤和 css 的 link,main.css 在服務器端延遲 5 秒後返回。
<head>
<script>
document.onreadystatechange = function () {
if (document.readyState === "interactive") {
console.log('DOMContentLoaded', document.body.children);
}
}
</script>
<link rel="stylesheet" href="/main.css" />
</head>
複製代碼
main.css 文件以下:
div {
background: red;
}
複製代碼
效果以下:
能夠看到,頁面刷新時,當即打印出了 DOMContentLoaded,儘管 main.css 是在延遲 5 秒後返回的,也就是說在 css 加載完成以前 dom 就已經解析完成了,css 的加載並不會阻止 dom 的解析。此外,咱們並無看到藍色的正方形,而一直是一個紅色的正方形,這意味着瀏覽器在 css 加載解析完成前沒有渲染它後面的 dom(若是不是,則先看到藍色的正方形,再看到紅色的正方形),而是在 css 加載解析後再進行渲染,也就是說 css 會阻塞頁面的渲染。這種策略是可以說得通的,試想若是先呈現出一個樣子,一會又變一下,體驗會比較差,並且屢次渲染也浪費性能。
另外一方面,在最初的測試時 script 是 放在link 後邊的,以下:
<head>
<link rel="stylesheet" href="/main.css" />
<script>
document.onreadystatechange = function () {
if (document.readyState === "interactive") {
console.log('DOMContentLoaded', document.body.children);
}
}
</script>
</head>
複製代碼
結果是,等到 main.css 加載完成後纔打印了 DOMContentLoaded,這彷佛和 css 不阻止 dom 解析相悖。事實上,因爲 script 可能去獲取 style 信息,若是 css 沒有加載完成,顯然不可以獲取正確的信息,所以部分瀏覽器會直接阻止後續 script 的執行。
須要說明的是以上全部結論在不一樣瀏覽器不一樣的版本,所採起的策略並不徹底一致,好比腳本加載執行時,chrome(v:74)會繼續下載 link 指定的文件,而 safari(v:12.0.2)link 文件 的下載會被阻塞。