阻塞渲染分为两类:
CSS阻塞渲染
从浏览器系列 — 渲染原理及过程中可得知第3步在构建渲染树时,需要完备的DOM树,CSSOM树,而CSSOM的解析可能会阻塞DOM解析,或者CSSOM树的未完成会阻塞渲染树的构建,这就是CSS阻塞渲染
JS阻塞渲染
JS 可以查询和修改 DOM 与 CSSOM,所以当 HTML 解析器遇到一个 script 标记时,它会暂停构建 DOM,将控制权移交给 JS 引擎, HTML 解析器会等待 JS 引擎运行完毕,这就是所谓的JS阻塞渲染
CSS阻塞渲染
1. 阻塞 DOM 解析(DOM树一直无法构建出来)
<body>
<h1>red1</h1>
<link rel="stylesheet" href="https://www.youtube.com/file.css"><!--这里会卡一段时间-->
<h1>red2</h1>
</body>
复制代码
- 可以看到页面依旧处于白屏状态
- 点击浏览器的停止加载按钮,red1内容会被渲染出来,此时查看Dom树,发现是没有
<h1>red2</h1>这个节点的 - 当资源下载失败时,
<h1>red2</h1>这个DOM节点才会被解析到,然后渲染出来
2. 阻塞渲染树的构建(CSSOM树一直无法构建出来)
<head>
<link rel="stylesheet" href="https://www.youtube.com/file.css"><!--这里会卡一段时间-->
<link rel="stylesheet" href="./link.css">
</head>
复制代码
- 在外网样式资源下载完成前,页面将会处于白屏现象
- 如阻塞渲染的样式资源下载超时报错,则会跳过,会使用已经下载完成的CSS资源做解析构建CSSOM
- 所以在等待一段时间后(资源下载超时后)页面才会显示出来
如何解决CSS阻塞渲染
1. CSS引入的位置——针对阻塞 DOM 解析
如果把<style>、<link>标签放在<body>里面,比如像上面<link>就会阻塞后面的<h1> DOM 节点的解析
一般我们把<style>、<link>放在<head>里面,提前加载好CSS资源,那么<h1> DOM 节点要解析时<link>那部分可能已经“完成”(超时跳过)了
2. 媒体查询的方式——针对阻塞CSSOM树的构建
<!-- 适用于所有情况,始终阻塞渲染 -->
<link href="style.css" rel="stylesheet">
<!-- 网页首次加载时,只在打印内容时适用 -->
<link href="print.css" rel="stylesheet" media="print">
<!-- 如果不是在打印内容时,该样式表不阻塞渲染 -->
<!-- 符合条件时浏览器将阻塞渲染,直至样式表下载并处理完毕 -->
<link href="other.css" rel="stylesheet" media="(max-width: 400px)">
<!-- 如果不满足条件,不会阻塞渲染,但依旧会请求下载对应的资源 -->
复制代码
JS阻塞渲染
JS阻塞渲染与CSS阻塞渲染的最大区别在于css的解析是可预测的,而JS阻塞渲染是不可预测的,因为JS可能随时修改DOM节点,乃至动态加载
1. 内联脚本阻塞渲染
<body>
<h1>AAA</h1>
<script>
let d = Date.now()
while (Date.now() < d + 1000 * 3) { }
</script>
<h2>BBB</h2>
</body>
复制代码
- 刚加载时
页面白屏,3秒后才会渲染出内容 - 说明内联JS会阻塞 DOM 解析和渲染,并且会一直阻塞
2. 外联同步脚本阻塞渲染
<body>
<h1>AAA</h1>
<script src="./test.js"></script>
<h2>BBB</h2>
</body>
复制代码
// test.js
let d = Date.now()
while (Date.now() < d + 1000 * 3) { }
复制代码
- 可以看到一开始就会渲染出
AAA,3s后才渲染出BBB - 说明外联脚本也会阻塞DOM解析与渲染,但是
因为无法确定脚本中的内容,所以会优先渲染一次已经构建DOM,确保加载的脚本能取得最新的DOM
如何解决JS阻塞渲染
1. <script>引入的位置
一般把<script>标签放在<body>的最后的位置,先进行 DOM 解析再加载/执行 JS 脚本,比如像上面<script>就会阻塞整个页面的渲染,使页面处于白屏状态
如果我们把<link>放在<head>里面,提前加载好CSS资源,那么<h1> DOM 节点要解析时<link>那部分可能已经“完成”(超时跳过)了
2. defer 和 async 属性
内联脚本阻塞渲染
在上面内联JS脚本阻塞渲染的例子中使用defer或async属性
<body>
<h1>AAA</h1>
<script defer/async>
let d = Date.now()
while (Date.now() < d + 1000 * 3) { }
</script>
<h2>BBB</h2>
</body>
复制代码
这里即使使用了defer或async属性也无济于事,说明这两种属性不能解决内联脚本阻塞问题
外联脚本阻塞渲染
在上面外联JS脚本阻塞渲染的例子中如果使用defer或async属性
<body>
<h1>AAA</h1>
<script defer/async src="./test.js"></script>
<h2>BBB</h2>
</body>
复制代码
// test.js
let d = Date.now()
while (Date.now() < d + 1000 * 3) { }
console.log('render success');
复制代码
这里可以看到一开始就会渲染出AAA、BBB,3s后打印render success,说明这两种属性可以解决外联脚本阻塞问题
既有内联脚本又有外联脚本
<body>
<h1>AAA</h1>
<script defer/async src="./test.js"></script>
<script>
let k = Date.now()
while (Date.now() < k + 1000 * 4) { }
</script>
<h1>BBB</h1>
</body>
复制代码
// test.js
let d = Date.now()
while (Date.now() < d + 1000 * 4) { }
console.log('render success');
复制代码
- 4s 后可以看到渲染出
AAA - 又经过 4s 后渲染出
BBB,同时控制台打印render success
如果去掉defer/async则AAA和BBB需要经过8s才会渲染出来,并且这期间一直处于页面白屏的状态
defer 和 async 的区别
我们由一张图直观看出
- 绿色线 代表
DOM 解析 - 灰色线 代表
DOM 解析被阻塞期间 - 紫色线 代表
JS 脚本的网络读取 - 红色线 代表
JS 脚本的执行时间
很明显看出 defer 和 async 的区别在于:
- async 加载完
即刻执行,此时阻塞 DOM 解析 - defer 加载完
等待 DOM 解析完成后再执行
从此可以总结出 defer 和 async 的应用场景区别:
- 如果你的脚本代码
依赖于页面中的DOM元素(文档是否解析完毕),或者被其他脚本文件依赖,则使用 defer 属性
比如:评论框、代码语法高亮
- 如果你的脚本并
不关心页面中的DOM元素(文档是否解析完毕),并且也不会产生其他脚本需要的数据,则使用 async 属性
比如:百度统计
附加问题
如何解决CSS阻塞渲染?
- 针对阻塞 DOM 解析——CSS引入位置
- 针对阻塞 CSSOM 树的构建——媒体查询
如何解决JS阻塞渲染?
- JS 引入位置
- defer 和 async 属性
defer和async的区别
- 原理上:加载和执行是否分开
- 应用上:脚本是否需要 DOM 解析完成才执行























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)