要了解这个过程,我们首先得知道一个名词——关键路径渲染(Critical Rendering Path
,它是指与当前用户操作有关的内容。例如用户在浏览器中打开一个页面,其中页面所显示的东西就是当前用户操作相关的内容,也就是浏览器从服务器那里收到的HTML、css、JavaScript等相关资源,然后经过一系列处理后渲染出的web页面。
1.浏览器渲染过程
先解释一下几个概念:
DOM Tree
:浏览器将HTML解析成树形的数据结构。
CSS Rule Tree:浏览器将CSS解析成树形的数据结构。
Render Tree: DOM和CSSOM合并后生成Render Tree。
Layout: 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置。
Display: 按照算出来的规则,通过显卡,把内容画到屏幕上。
- 处理HTML标记并构建DOM树
- 处理css标记并构建CSSOM树
- 将DOM树和CSSOM树合并构建一个渲染树
- 渲染树布局,浏览器通过Render Tree计算出每个节点对象在页面的确切大小和位置
- 渲染树绘制,浏览器会调用渲染器的paint()方法在屏幕上显示内容
2. 阻塞渲染
我们知道,现代浏览器总是并行加载资源,例如当HTML解析器被脚本阻塞时,解析器虽然会停止构建DOM树,但仍然会辨识该脚本后面的资源,并进行预加载。
对于css阻塞渲染:
css的加载并不会导致HTML解析和渲染的停止,但是会影响到js脚本的执行,因为js脚本不仅可以修改DOM,也可以读取修改CSSOM,所以在js脚本执行前,浏览器必须保证CSS文件完全加载并解析完成,即CSSOM树完全构建好。这就导致了js执行的延迟,渲染延迟。(这就是css阻塞js执行,阻塞渲染的根本原因)
有一些解决办法:
- 对css进行精简并尽快提供
- 可以用媒体类型(会加载不会阻塞)
- 用媒体查询(会加载,只有在符合的设备上才会进行阻塞)
另外:
(1). 当浏览器遇到一个script 标记时,DOM 构建将暂停,直至脚本完成执行;
(2). JavaScript 可以查询和修改 DOM 与 CSSOM;
(3). CSSOM 构建时,JavaScript 执行将暂停,直至 CSSOM 就绪。
所以script 标签的放置位置很重要,可以遵循以下原则:
- 在引入顺序上,CSS 资源先于 JavaScript 资源
- JavaScript 应尽量少影响 DOM 的构建
对于js阻塞,有两种解决方法(对于inline-script无效):
defer属性(<script src="" defer></script>
)
这是延迟执行引入的js脚本(即脚本加载是不会导致解析停止,等到document全部解析完毕后,defer-script也加载完毕后,在执行所有的defer-script加载的js代码,再触发Domcontentloaded)
async属性(<script src="" async></script>
)
(1)这是异步执行引入的js脚本文件
(2)与defer的区别是async会在加载完成后就执行,但是不会影响阻塞到解析和渲染。但是还是会阻塞load事件,所以async-script会可能在DOMcontentloaded触发前或后执行,但是一定会在load事件前触发。
3. 改变阻塞渲染
对于CSS:
对应于上文中提到的解决办法,除了对css进行精简并尽快提供外,其他进行如下示例
<link href="index.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 30em) and (orientation: landscape)">
- 第一个资源会加载并阻塞
- 第二个设置了媒体类型,会加载但不会阻塞,print声明只在打印网页时使用
- 第三个资源提供了媒体查询,会在符合条件时阻塞渲染
**js: **
defer
:
<script src='app1.js' defer></script>
<script src='app2.js' defer></script>
<script src='app3.js' defer></script>
defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时HTML 并未停止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载 JavaScript 代码,然后触发 DOMContentLoaded 事件。
defer 不会改变 script 中代码的执行顺序,示例代码会按照1、2、3的顺序执行。所以,defer 与普通 script 相比,有两点区别:
- 载入 JavaScript 文件时不阻塞 HTML 的解析
- 执行阶段被放到 HTML 标签解析完成之后
async
:
<script src='app.js' async></script>
<script src='ad.js' async></script>
<script src='statistics.js' async></script>
async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行——无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发之后。需要注意的是,这种方法加载的 JavaScript 依然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。
从上一段也能推出,多个 async-script 的执行顺序是不确定的。
值得注意的是,向 document 动态添加 script 标签时,async 属性默认是 true,也就是使用 document.createElement
创建的 script 默认是异步的,如果想要同步执行,必须手动设置属性值为false。
console.log(document.createElement("script").async); // true
https://www.jianshu.com/p/e53141edca6d
https://www.cnblogs.com/slly/p/6640761.html
https://www.imooc.com/article/40004