# 一、浏览器概述
目前的主流浏览器有 5 个:Internet Explorer、Firefox、Safari、Chrome 和 Opera 浏览器。根据 StatCounter 浏览器统计数据,目前(截止 2019 年 5 月)Firefox、Safari 和 Chrome 浏览器的总市场占有率将近 83.66%。由此可见,如今开放源代码浏览器在浏览器市场中占据了非常坚实的部分。
以上 5 种浏览器由于有着不同的浏览器内核,造成同样的 html 页面有着不同呈现。Internet Explorer 的内核是 Trident;Firefox 的内核是 Gecko;Chrome、Safari 内核是 Webkit;Opera 的内核则是 Presto。
# 二、浏览器渲染过程
我们先大致看下浏览器渲染关键路径图:
# 1、HTML 解析,构建 DOM 树
浏览器从网络或硬盘中获得 HTML 字节数据后会经过以下流程将字节解析为 DOM 树:
- 字符编码:先将 HTML 的原始字节数据转换为文件指定编码的字符。
- 令牌化:然后浏览器会根据 HTML 规范来将字符串转换成各种令牌(如
<html>
、<body>
、<p>
这样的标签以及标签中的字符串和属性等都会被转化为令牌,每个令牌具有特殊含义和规则)。 - 生成节点对象:接着每个令牌都会被转换成定义其属性和规则的对象,即节点对象。
- 构建 DOM 树:最后将节点对象构建成树形结构,即 DOM 树。HTML 标签之间有复杂的父子关系,树形结构刚好可以诠释这样的关系。
下面通过一段 HTML 代码与配图更好的理解 “字节 -> 字符 -> 令牌 -> 节点对象 -> 对象模型” 这个过程:
<html> | |
<head> | |
<meta name="viewport" content="width=device-width,initial-scale=1"> | |
<link href="style.css" rel="stylesheet"> | |
<title>Critical Path</title> | |
</head> | |
<body> | |
<p>Hello <span>web performance</span> students!</p> | |
<div><img src="test.png"></div> | |
</body> | |
</html> |
# 2、CSS 解析,构建 CSSOM 树
浏览器解析遇到 <link>
外链、 <style>
内嵌或 HTML 标签 style
属性内联时,浏览器就开始解析 CSS,像构建 DOM 树一样构建 CSSOM 树。style.css 的代码如下:
body { font-size: 16px } | |
p { font-weight: bold } | |
span { color: red } | |
p span { display: none } | |
img { float: right } |
# CSSOM 树
CSSOM
树描述了选择器之间的层级关系,可以在浏览器开发者工具 Console 面板通过 document.styleSheets
命名查看,如下图:
# 3、构建 Render Tree
在构建了 DOM 树和 CSSOM 树之后,浏览器只是拥有 2 个相互独立的对象集合,DOM 树描述的文档结构和内容,CSSOM 树描述了对应文档的样式规则,想要渲染出页面,就需要将 DOM 树、CSSOM 树结合在一起,构建渲染树。
# 4、Layout 布局
渲染树构建好后,浏览器得到了每个节点的内容与样式,下一步就是需要计算每个节点在浏览器窗口的确切位置与大小,即 layout 布局。
布局阶段,从渲染树的根节点开始遍历,采用盒子模型的模式来表示每个节点与其他元素之间的距离,从而确定每个元素在屏幕内的位置与大小。
盒子模型:包括外边距(margin),内边距(padding),边框(border),内容(content)。标准盒子模型 width/height = content;IE 盒子模型 width/height = content + padding + border。
# 5、Paint 绘制页面
当 Layout 布局完成后,浏览器渲染引擎开始将渲染树绘制成像,绘制阶段主要经历了 "Layer 分层、compositing 合成"2 个过程后成像在屏幕上。绘制所需要的时间跟 CSS 样式的复杂度成正比。
# Layer 分层树构建
页面元素设计比较复杂的场景,比如 3D 变化、页面滚动、z-index 在 z 轴排序需要渲染引擎为特定的节点生成专用图层,从而生成了一棵分层树。
Chrome 开发者工具的 Layers 面板可以查看到页面分层的情况(以掘金首页为例):
# compositing 合成图层
当完成了分层树的构建后,渲染引擎准备绘制图层,它会把图层的绘制分成很多个绘制指令,然后把这些指令按顺序组成一个待绘制列表。绘制列表提交给渲染引擎的合成线程处理,形成合成位图,最终呈现在屏幕上。
# 三、渲染优化方案
# 1、优化渲染关键路径方案
通过优化渲染关键路径,可以优化页面渲染性能,减少页面白屏时间。
优化 JS
JavaScript 文件加载会阻塞 DOM 树的构建,可以给
<script>
标签添加异步属性 async,这样浏览器的 HTML 解析就不会被 js 文件阻塞。优化 CSS
浏览器每次遇到
<link>
标签时,浏览器就需要向服务器发出请求获得 CSS 文件,然后才继续构建 DOM 树和 CSSOM 树,可以合并所有 CSS 成一个文件,减少 HTTP 请求,减少关键资源往返加载的时间,优化渲染速度。
# 2、其他优化方案
加载部分 HTML
浏览器先加载主要 HTML 初始化静态部分,动态变化的 HTML 内容通过 Ajax 请求加载。这样可以减少浏览器构建 DOM 树的工作量,让用户感觉页面加载速度很快。压缩
对 HTML、CSS、JavaScript 这些文件去除冗余字符(例如不必要的注释、空格符和换行符等),再进行压缩,减小文件数据大小,加快浏览器解析文件编码。图片加载优化
- 小图标合并成雪碧图,进而减少 img 的 HTTP 请求次数;
- 图片加载较多时,采用懒加载的方案,用户滚动页面可视区时再加载渲染图片。
HTTP 缓存
浏览器自带了 HTTP 缓存的功能,只需要确保每个服务器响应的头部都包含了以下的属性:ETag
ETag 是一个传递验证令牌,它对资源的更新进行检查,如果资源未发生变化时不会传送任何数据。当浏览器发送一个请求时,会把 ETag 一起发送到服务器,服务器会根据当前资源核对令牌(ETag 通常是对内容进行 Hash 后得出的一个指纹),如果资源未发生变化,服务器将返回 304 Not Modified 响应,这时浏览器不必再次下载资源,而是继续复用缓存。
Cache-Control
Cache-Control 定义了缓存的策略,它规定在什么条件下可以缓存响应以及可以缓存多久。
- no-cache
no-cache 表示必须先与服务器确认返回的响应是否发生了变化,然后才能使用该响应来满足后续对同一网址的请求(每次都会根据 ETag 对服务器发送请求来确认变化,如果未发生变化,浏览器不会下载资源)。no-store 直接禁止浏览器以及所有中间缓存存储任何版本的返回响应。简单的说,该策略会禁止任何缓存,每次发送请求时,都会完整地下载服务器的响应。
- public&private
如果响应被标记为 public,则即使它有关联的 HTTP 身份验证,甚至响应状态代码通常无法缓存,浏览器也可以缓存响应。如果响应被标记为 private,那么这个响应通常只为单个用户缓存,因此不允许任何中间缓存(CDN)对其进行缓存,private 一般用在缓存用户私人信息页面。
c、max-age: max-age 定义了从请求时间开始,缓存的最长时间,单位为秒。