渲染阻塞问题
之前在学习浏览器的渲染原理的时候我们就知道:因为浏览器一次只能开启一个渲染主线程,所以当浏览器解析到script标签时会停止DOM树的构建,转而去执行script,如果script中引用的是外部脚本,则浏览器会先从网络上下载script,下载完毕后执行script,当script执行完毕之后继续构建DOM树
DOM树作为外部脚本访问DOM节点的接口,当我们调用诸如 document.getElementById 的方法时,返回的元素是一个 DOM 节点。每个 DOM 节点都有许多可以用来访问和更改它的函数,用户看到的内容也会相应地发生变化
因为script中的代码能动态的修改DOM树中的节点,所以在浏览器解析到script标签时必须先停止DOM树的构建,在执行完script之后继续开始构建DOM树,这也是为什么我们写html时script标签总是放在body标签最后
浏览器不仅会在解析HTML的时候构建DOM树,还会在解析CSS的时候构建CSSOM树,那么在构建CSSOM树的时候会发生渲染阻塞吗
事实上,因为CSSOM并不会更改DOM,所以构建CSSOM本身并不能阻塞渲染,但在JavaScript中我们能动态的访问与修改元素的CSS,而在运行script时如果CSSOM尚未构建完成则浏览器会转而构建CSSOM,等CSSOM构建完成后再运行script
无样式内容闪现
无样式内容闪现,简称为FOUC,在浏览器构建完整个DOM,CSSOM尚未构建完时,页面会短暂的展示一个没有CSS的页面,但当CSSOM构建完成后页面就会变成有CSS样式的样子,这种突然的视觉变化被称为无内容闪现
无内容闪现会给用户带来极其不好的体验,为了尽量避免这种情况的发生,我们通常会将CSS放在HTML顶部,这样当浏览器解析HTML文档时会首先解析CSSOM,然后再解析DOM
然而由script标签所带来的阻塞渲染问题依旧没有解决,随着浏览器的日渐成熟,浏览器也为我们提供了属性或值,这些属性或值被称为资源提示关键词,通过使用这些关键词我们就能很好的处理阻塞渲染的问题
defer与async
defer和async都是script标签的一个布尔属性,他们都能实现避免渲染阻塞问题的发生,但具体又有些细微的不同
defer
<script type="text/javascript" defer src="./index.js"></script>
在script标签中的defer属性,如果为true的话浏览器会推迟这段script的执行,浏览器会在文档被解析后,但在触发 DOMContentLoaded 事件之前执行
需要注意的是,defer的是否生效与该script元素的src属性是否设置密切相关
async
<script type="text/javascript" async src="./index.js"></script>
async属性同样是个布尔属性,如果是普通脚本,那么普通脚本会被并行请求,并尽快解析和执行。如果是模块脚本,它们也会被并行请求,并尽快解析和执行,也就是说,在下载JavaScript脚本的过程中并不会导致渲染阻塞,但执行JavaScript代码时依旧会阻塞DOM树的构建
总结:
- 如果该script元素的src属性为空,则defer和async属性不生效
- 如果该script元素的type属性为空或者为一个JavaScript MIME类型时,该script为普通脚本,普通脚本会受到defer和async的影响,但这些的前提为src属性不为空
- 如果该script元素的type属性为module时,该script为JavaScript模块脚本,该类型脚本不受defer的影响,但会受到async的影响,与src属性无关
- 如果该script元素的type属性为其他值时则defer和async属性不生效
- 即使已经指定了async属性,也可以同时指定defer属性,从而可以使不支持async属性的浏览器通过defer来避免渲染阻塞的发生
preload
<link rel="preload" href="./index.css" as="style">
<link rel="preload" href="./index.js" as="script">
preload是link元素的rel属性值,该属性可以通过显示声明一个资源来告诉浏览器这个资源需要首先加载,而不是用的时候再加载,从而使在之后用到这个资源的时候无需等待加载,可以直接使用,preload通过as属性来指定预加载资源的类型
使用preload有如下优点:
- 使用preload我们可以将重要的资源提前加载,从而提高页面的流畅性
- 通过as属性,浏览器可以决定哪些资源能够复用
- 通过as属性,浏览器可以判断请求是否符合内容安全策略(CSP,如XSS,数据注入攻击等)
prefetch与prerender
这两个关键词都是link元素rel的属性值,与preload类似,但这两个关键词会在空余时间执行
prefetch
<link rel="prefetch" href="./message.css" as="style">
prefetch会在页面的空余时间来加载指定的资源,通过prefetch获取的资源通常会被放入缓存至少5分钟,当页面跳转时已发送的prefetch也不会中断
prefetch通常用于请求除首页外的其他页面资源,通过prefetch我们可以提高首屏后其他页面的渲染速度
prerender
<link rel="prerender" href="https://www.baidu.com" as="style">
prerender和prefetch类似,但prerender会在页面空余时间直接加载整个页面,而不是某个资源