浏览器内核
在研究浏览器微观的运行机制之前, 我们首先先对浏览器内核有一个宏观概念上理解(要对以下内容有一个大体的印象, 最好脑肌肉记忆为佳).
一, 概念
浏览器最重要或者说核心的部分是 "Rendering Engine", 可大概译为 "渲染引擎", 不过我们一般习惯将之称为 "浏览器内核". 负责对网页语法的解释 (如标准通用标记语言下的一个应用 https://baike.baidu.com/item/html , https://baike.baidu.com/item/JavaScript ) 并渲染 (显示) 网页. 所以, 通常所谓的浏览器内核也就是浏览器所采用的渲染引擎, 渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息. 不同的浏览器内核对网页编写语法的解释也有不同, 因此同一网页在不同的内核的浏览器里的渲染 (显示) 效果也可能不同, 这也是网页编写者需要在不同内核的浏览器中测试网页显示效果的原因.
---- 摘自百度百科
浏览器内核分成两部分: 渲染引擎 (Layout Engine 或者 Rendering Engine) 和 JS 引擎.
tips: 早期渲染引擎和 JS 引擎并没有十分明确的区分, 但随着 JS 引擎越来越独立, 内核也成了渲染引擎的代称.
渲染引擎又包括 HTML 解释器, CSS 解释器, 布局, 网络, 存储, 图形, 音视频, 图片解码器等等零部件.
---- 来自掘金小册关于内核的介绍总结
二, 什么是渲染过程
简单说, 浏览器渲染引擎根据 HTML 文件, 调用浏览器各个部件, 将网页资源源代码转换为图像结果, 这个过程就是渲染过程. 如下:
HTML\CSS\JS 资源 → 浏览器内核→ 图像, 视图
对于上面三个过程, 我们对第一个与第三个都比较熟悉, 无非就是在本地写下一行代码, 直接显示在页面上, 而我们不熟悉的东西就是中间那个浏览器内核. 这也是我们在此刻需要关注的核心.
我们需要从这几个方面去进行关注:
HTML 解释器 ----将 HTML 文档生成 DOM 树.
CSS 解释器 ----解析 CSS 文档, 生成样式规则.
布局计算模块 ----布局计算每个对象的精确位置和大小.
视图绘制模块 ----进行具体节点的图像绘制, 将像素渲染到屏幕上.
JavaScript 引擎 ----编译执行 JavaScript 代码.
三, 浏览器渲染过程解析
渲染过程(摘自掘金).PNG
为了便于理解这个过程, 为了让这个渲染过程更清晰一点, 我们需要捋一捋在这个过程中特别重要的 ---- 树
树(依然摘自掘金).PNG
最后, 在通读以上内容后, 基于 "树" 的概念, 我们做个总结:
渲染过程就是基于 HTML 构建一个 DOM 树, 这棵 DOM 树与 CSS 解释器解析出的 CSSOM 相结合, 就有了布局渲染树. 最后浏览器以布局渲染树为蓝本, 去计算布局并绘制图像, 我们页面的初次渲染就大功告成了.
然后, 接下来每当有一个新元素加入到这个 DOM 树上的时候, 浏览器便会通过 CSS 引擎查遍 CSS 样式表, 找到符合该元素的样式规则应用到这个元素上, 然后再重新去绘制它.
然后, 重点来了, 浏览器通过 CSS 引擎查遍 CSS 样式表, 这个操作是要花时间的啊, 所以我们要做的是尽量的将这个浏览器通过 CSS 引擎查遍 CSS 样式表的时间进行压缩 -----CSS 样式表的优化!
四, 优化 - 基于渲染流程的 CSS 优化建议
首先在这里我们要了解浏览器通过 CSS 引擎查遍 CSS 样式表它是怎么个查法, 有个小知识是这么说的:
CSS 引擎查找样式表, 对每条规则都按从右到左的顺序去匹配.--- 注意是从右到左, 不是我们平时在写代码时候的从左到右
举个例子, 经典通配符:
*{}, 这个通配符会匹配所有元素, 所以浏览器必须去遍历每一个元素!
于是关于 CSS 的优化直接给出结论:
避免使用通配符, 只对需要用到的元素进行选择.
关注可以通过继承实现的属性, 避免重复匹配重复定义.
少用标签选择器. 如果可以, 用类选择器替代.
不要画蛇添足, id 和 class 选择器不应该被多余的标签选择器拖后腿.
减少嵌套. 后代选择器的开销是最高的, 因此我们应该尽量将选择器的深度降到最低(最高不要超过三层), 尽可能使用类来关联每一个标签元素.
五, 优化 - 关于 CSS 与 JS 的阻塞优化, 即 CSS 与 JS 的加载顺序优化
HTML 的阻塞是显而易见的, 即渲染成 DOM 过程的阻塞, 这是天经地义不可改变的.
因此, 我们来看看 CSS 与 JS 的阻塞优化.
CSS 阻塞
回到上面的图片示例, 我们知道 DOM 和 CSSOM 合力才能构建渲染树(即都准备好变后才可渲染). 因此在不考虑 DOM 阻塞的情况下, 这个时候我们考虑 CSSOM 阻塞.
关于上面这句话换种说法: DOM 与 CSSOM 同时构建完毕, 才可走下一步构建渲染树, 即使 DOM 早已渲染完毕, 若 CSSOM 没准备完毕, 那就一直等着.
然后, 回到 CSSOM 的构建, 我们知道当开始解析 HTML 后, 解析到 link 标签或者 style 标签后, CSS 才开始上场, CSSOM 的构建才开始, 因此大多数的时候, DOM 都是在等待 CSSOM, 总结为:
CSS 是阻塞渲染的资源. 需要将它尽早, 尽快地下载到客户端, 以便缩短首次渲染的时间.
其实, 这有一点绕的几句话就是来说明关于这个 CSS 原理, 结论, 现在很多团队都已经做到了尽早 (将 CSS 放在 head 标签里) 和尽快(启用 CDN 实现静态资源加载速度的优化).
JS 阻塞
前面一直在说 HTML, 说 CSS, 还没提到 JS.
这是因为在首次渲染过程中, JS 并不是一个非登场不可的角色 -- 没有 JS,CSSOM 和 DOM , 照样可以组成渲染树, 页面依然会呈现 -- 即使它死气沉沉, 毫无交互 JS 终于出来了.
JS 的作用在于修改, 它帮助我们修改网页的方方面面: 内容, 样式以及它如何响应用户交互. 这 "方方面面" 的修改, 本质上都是对 DOM 和 CSSDOM 进行修改. 因此 JS 的执行会阻止 CSSOM, 在我们不作显式声明的情况下, 它也会阻塞 DOM.
JS 引擎是独立于渲染引擎存在的. 我们的 JS 代码在文档的何处插入, 就在何处执行. 当 HTML 解析器遇到一个 script 标签时, 它会暂停渲染过程, 将控制权交给 JS 引擎. JS 引擎对内联的 JS 代码会直接执行, 对外部 JS 文件还要先获取到脚本, 再进行执行. 等 JS 引擎运行完毕, 浏览器又会把控制权还给渲染引擎, 继续 CSSOM 和 DOM 的构建. 因此与其说是 JS 把 CSS 和 HTML 阻塞了, 不如说是 JS 引擎抢走了渲染引擎的控制权.
现在理解了阻塞的表现与原理, 我们开始思考一个问题. 浏览器之所以让 JS 阻塞其它的活动, 是因为它不知道 JS 会做什么改变, 担心如果不阻止后续的操作, 会造成混乱. 但是我们是写 JS 的人, 我们知道 JS 会做什么改变. 假如我们可以确认一个 JS 文件的执行时机并不一定非要是此时此刻, 我们就可以通过对它使用 defer 和 async 来避免不必要的阻塞, 这里我们就引出了外部 JS 的三种加载方式.
JS 的三种加载方式
正常模式:
<script src="index.js"></script>
这种情况下 JS 会阻塞浏览器, 浏览器必须等待 index.JS 加载和执行完毕才能去做其它事情.
async 模式:
<script async src="index.js"></script>
async 模式下, JS 不会阻塞浏览器做任何其它的事情. 它的加载是异步的, 当它加载结束, JS 脚本会立即执行.
defer 模式:
<script defer src="index.js"></script>
defer 模式下, JS 的加载是异步的, 执行是被推迟的. 等整个文档解析完成, DOMContentLoaded 事件即将被触发时, 被标记了 defer 的 JS 文件才会开始依次执行.
从应用的角度来说, 一般当我们的脚本与 DOM 元素和其它脚本之间的依赖关系不强时, 我们会选用 async; 当脚本依赖于 DOM 元素和其它脚本的执行结果时, 我们会选用 defer.
通过审时度势地向 script 标签添加 async/defer, 我们就可以告诉浏览器在等待脚本可用期间不阻止其它的工作, 这样可以显著提升性能.
本节结语
我们知道, 当 JS 登场时, 往往意味着对 DOM 的操作. DOM 操作所导致的性能开销的 "昂贵", 大家可能早就有所耳闻, 雅虎军规里很重要的一条就是 "尽量减少 DOM 访问".
那么 DOM 到底为什么慢, 如何去规避这个慢, 这就需要我们去了解一下 CSS 中的回流 (Reflow) 与重绘(Repaint).
本节完.
来源: http://www.jianshu.com/p/5093168b5907