在探讨 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 文件 的下载会被阻塞。