一、浏览器概述

目前的主流浏览器有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。

二、浏览器渲染过程

我们先大致看下浏览器渲染关键路径图: image.png

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>

img

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 }

img

CSSOM树

CSSOM 树描述了选择器之间的层级关系,可以在浏览器开发者工具 Console 面板通过 document.styleSheets 命名查看,如下图: image.png

3、构建 Render Tree

在构建了DOM树和CSSOM树之后,浏览器只是拥有2个相互独立的对象集合,DOM树描述的文档结构和内容,CSSOM树描述了对应文档的样式规则,想要渲染出页面,就需要将DOM树、CSSOM树结合在一起,构建渲染树。

img

4、Layout 布局

渲染树构建好后,浏览器得到了每个节点的内容与样式,下一步就是需要计算每个节点在浏览器窗口的确切位置与大小,即layout布局。
布局阶段,从渲染树的根节点开始遍历,采用盒子模型的模式来表示每个节点与其他元素之间的距离,从而确定每个元素在屏幕内的位置与大小。

盒子模型:包括外边距(margin),内边距(padding),边框(border),内容(content)。标准盒子模型width/height = content;IE盒子模型width/height = content + padding + border。

img

5、Paint 绘制页面

当Layout布局完成后,浏览器渲染引擎开始将渲染树绘制成像,绘制阶段主要经历了”Layer分层、compositing合成”2个过程后成像在屏幕上。绘制所需要的时间跟CSS样式的复杂度成正比。

Layer 分层树构建

页面元素设计比较复杂的场景,比如3D变化、页面滚动、z-index在 z 轴排序需要渲染引擎为特定的节点生成专用图层,从而生成了一棵分层树。
Chrome开发者工具的 Layers 面板可以查看到页面分层的情况(以掘金首页为例): image.png

compositing 合成图层

当完成了分层树的构建后,渲染引擎准备绘制图层,它会把图层的绘制分成很多个绘制指令,然后把这些指令按顺序组成一个待绘制列表。绘制列表提交给渲染引擎的合成线程处理,形成合成位图,最终呈现在屏幕上。

image.png

三、渲染优化方案

1、优化渲染关键路径方案

通过优化渲染关键路径,可以优化页面渲染性能,减少页面白屏时间。

  • 优化JS

    JavaScript文件加载会阻塞DOM树的构建,可以给<script>标签添加异步属性async,这样浏览器的HTML解析就不会被js文件阻塞。

  • 优化CSS

    浏览器每次遇到<link>标签时,浏览器就需要向服务器发出请求获得CSS文件,然后才继续构建DOM树和CSSOM树,可以合并所有CSS成一个文件,减少HTTP请求,减少关键资源往返加载的时间,优化渲染速度。

2、其他优化方案

  • 加载部分HTML
    浏览器先加载主要HTML初始化静态部分,动态变化的HTML内容通过Ajax请求加载。这样可以减少浏览器构建DOM树的工作量,让用户感觉页面加载速度很快。

  • 压缩
    对HTML、CSS、JavaScript这些文件去除冗余字符(例如不必要的注释、空格符和换行符等),再进行压缩,减小文件数据大小,加快浏览器解析文件编码。

  • 图片加载优化

    1. 小图标合并成雪碧图,进而减少img的HTTP请求次数;
    2. 图片加载较多时,采用懒加载的方案,用户滚动页面可视区时再加载渲染图片。
  • HTTP缓存
    浏览器自带了HTTP缓存的功能,只需要确保每个服务器响应的头部都包含了以下的属性:

    1. ETag

      ETag是一个传递验证令牌,它对资源的更新进行检查,如果资源未发生变化时不会传送任何数据。当浏览器发送一个请求时,会把ETag一起发送到服务器,服务器会根据当前资源核对令牌(ETag通常是对内容进行Hash后得出的一个指纹),如果资源未发生变化,服务器将返回304 Not Modified响应,这时浏览器不必再次下载资源,而是继续复用缓存。

    2. Cache-Control

      Cache-Control定义了缓存的策略,它规定在什么条件下可以缓存响应以及可以缓存多久。

      1. no-cache

      no-cache表示必须先与服务器确认返回的响应是否发生了变化,然后才能使用该响应来满足后续对同一网址的请求(每次都会根据ETag对服务器发送请求来确认变化,如果未发生变化,浏览器不会下载资源)。no-store直接禁止浏览器以及所有中间缓存存储任何版本的返回响应。简单的说,该策略会禁止任何缓存,每次发送请求时,都会完整地下载服务器的响应。

      1. public&private

      如果响应被标记为public,则即使它有关联的HTTP身份验证,甚至响应状态代码通常无法缓存,浏览器也可以缓存响应。如果响应被标记为private,那么这个响应通常只为单个用户缓存,因此不允许任何中间缓存(CDN)对其进行缓存,private一般用在缓存用户私人信息页面。
      c、max-age: max-age定义了从请求时间开始,缓存的最长时间,单位为秒。

img