浏览器的渲染引擎
- IE(Trident)
- Chrome(Blink)
- Firefox(Gecko)
- Opera(Blink)
- Safari(Webkit)
- UC(U3)
- QQ浏览器微信(X5/Blink)
Blink 渲染引擎占据了半壁江山,Blink 的前身都是由 Webkit 优化改造而来的。浏览器内核决定了浏览器解释网页语法的方式。不同的渲染引擎,从而也导致了浏览器的兼容性问题。
浏览器渲染“黑盒”
浏览器呈现网页这个过程,宛如一个黑盒。在这个神秘的黑盒中,有许多功能模块,内核内部的实现正是这些功能模块相互配合协同工作进行的。其中我们最需要关注的,就是 HTML 解释器、CSS 解释器、图层布局计算模块、视图绘制模块 与 JavaScript 引擎 这几大模块:
HTML 解释器:将 HTML 文档经过词法分析输出 DOM 树。
CSS 解释器:解析 CSS 文档, 生成样式规则。
图层布局计算模块:布局计算每个对象的精确位置和大小。
视图绘制模块:进行具体节点的图像绘制,将像素渲染到屏幕上。
JavaScript 引擎:编译执行 Javascript 代码。
渲染原理解析
- 解析HTML
首先是解析HTML/SVG/XHTML,这个过程主要是把 HTML/SVG/XHTML 文档解析为 DOM 树的过程。
如果遇到 <script> 标签会停止解析,先执行标签当中 JavaScript;如果是外联方式,也需要等待下载并且执行完对应的 JavaScript 代码,然后才能够继续执行解析 HTML 的工作。HTML解析完成后触发 DOMContentLoaded 事件,这里我们就可以操作 DOM了。
在这一步浏览器执行了所有的加载解析逻辑,在解析 HTML 的过程中发出了页面渲染所需的各种外部资源请求。 - 解析CSS
解析 HTML 和解析 CSS 是并行处理的。解析 CSS 遇到 <script> 标签,会阻塞 CSS 的解析。CSS 解析器将 CSS 解析成 CSSStyleSheet (也被叫做 CSSOM 树),这里的 CSSOM 树与 DOM 树结构类似。解析对应关系如下: - 计算样式
CSSOM 与 DOM 树 结合,生成生成页面 render 树。 - 计算图层布局
页面中所有元素的相对位置信息,大小等信息均在这一步得到计算。 - 绘制图层
在这一步中浏览器会根据我们的 DOM 代码结果,把每一个页面图层转换为像素,并对所有的媒体文件进行解码。 - 整合图层,得到页面
最后一步浏览器会合并各个图层,将数据由 CPU 输出给 GPU 最终绘制在屏幕上。(复杂的视图层会给这个阶段的 GPU 计算带来一些压力,在实际应用中为了优化动画性能,我们有时会手动区分不同的图层)。
渲染过程概括
- 当浏览器拿到一个网页后,首先浏览器会先解析HTML,如果遇到了外链的css,会一下载css,一边解析HTML。
- 当css下载完成后,会继续解析css,生成css Rules tree,不会影响到HTML的解析。
- 当遇到 <script> 标签时,一旦发现有对javascript的引用,就会立即下载脚本,同时阻断文档的解析,等脚本执行完成后,再开始文档的解析。
- 当DOM树和CSS规则树已经生成完毕后,构造 Rendering Tree。
- 调用系统渲染页面。
渲染优化
CSS 的阻塞
CSS 解析和HTML解析是同步进行的,那么一个 HTML 文档首先解析的肯定是 HTML,然后才是 CSS,这就导致了 HTML 解析完成后,往往需要等待 CSS 解析。如果 CSS 没有解析完成,我们就需要一直等,这里就是 CSS 阻塞了相关的渲染。
CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。
CSS 会阻塞后面内联 JavaScript 的执行。
我们往往把 CSS 样式表全部通过 <style> 标签内联到网页当中,或启用 CDN 实现静态资源加载速度的优化
JS 的阻塞
JS 引擎是独立于渲染引擎存在的。JavaScript 既会阻塞 HTML 解析,也会阻塞 CSS 解析。因此我们可以改变 JavaScript 的加载方式或者加载时机来进行优化:
- 尽量将 JavaScript 文件放在 body 的底部;
- body中间尽量不要写 <script> 标签;
- 使用 defer 和 async 来避免不必要的阻塞
JS的四种加载方式
正常模式:这种情况下 JS 会阻塞浏览器,浏览器必须等待 index.js 加载和执行完毕才能去做其它事情。
1
<script src="index.js"></script>
async 模式:JS 不会阻塞浏览器做任何其它的事情。它的加载是异步的,当它加载结束,JS 脚本会立即执行。使用 async 模式引入的多个 script 的执行顺序是不确定的。
1
<script async src="index.js"></script>
defer 模式:JS 的加载是异步的,执行是被推迟的。等整个文档解析完成、会执行所有由 defer-script 加载的 JavaScript 代码,然后触发 DOMContentLoaded 事件。使用 defer 模式引入的多个 script 是从上往下顺序执行的。
1
<script defer src="index.js"></script>
动态异步引入
使用 document.createElement 创建的 script 默认是异步的,示例如下:1
console.log(document.createElement("script").async); // true
从应用的角度来说,一般当我们的脚本与 DOM 元素和其它脚本之间的依赖关系不强时,我们会选用 async;当脚本依赖于 DOM 元素和其它脚本的执行结果时,我们会选用 defer。defer 一般用于业务代码。如第三方统计代码。
注意 async 与 defer 属性对于 inline-script 都是无效的。