浅析渲染引擎与前端优化

本文主要是两方面内容:

  • 浅析浏览器内核的工作原理(以 WebKit 2 为例)。
  • 浅析由浏览器内核想到的前端优化,或者说前端优化规则是从哪儿来的。

大家知道,大部分的 WEB 页面依托浏览器呈现,而浏览器能够将页面展示出来,基本依赖于浏览器的内核,即渲染引擎。今天以 Chrome 浏览器的内核 WebKit(更确切是 WebKit 分支 Blink,以下统称为 WebKit )为例,对渲染引擎如何展示页面做个简单、全面的了解。

111111

浏览器的渲染引擎及其依赖模块

渲染引擎主要是将 WEB 资源如 HTML、CSS、图片、JavaScript等经过一系列加工,最终呈现出展示的图像。渲染引擎主要包含了对这些资源解析的处理器,如 HTML 解释器、CSS 解释器、布局计算+绘图工具、JavaScript 引擎等。为了更好地呈现渲染效果,渲染引擎还会依赖网络栈、缓存机制、绘图工具、硬件加速机制等。

1-5

浏览器的渲染过程

浏览器的渲染过程,主要包括两大部分:网页资源加载过程渲染过程

3-1

上图将整个网页渲染的过程做了大致的剖析。以下我们按照数据流向,逐一详细剖析每个过程。

一、域名解析 DNS

当我们在浏览器中输入 URL 后,浏览器首先会进行域名解析。一般情况下,一次 DNS 域名解析大概需要 60-120 ms,一次 TCP 的三次握手需要 1.5 个 RTT(round-trip time)。WebKit 的方案是 采用 DNS 预取技术和 TCP 预连接技术。

DNS 预取技术利用现有 DNS 机制,提前解析网页中可能的网络连接。即对用户浏览网页中存在的链接,用较少的 CPU 和网络带宽来解析这些链接的域名或 IP 地址;等用户单击链接时,就会节省时间~ 特别是域名解析慢的时候~

同样,在地址栏输入链接时,候选项也会被默默地执行 DNS 预取~。在 DNS 预取后,会预先建立 TCP 连接。

对此前端优化建议:

  • 在页面中指定预取域名:<link rel=”dns-prefetch” href=”http://this-is-a.com”>
  • 大数据分析,推测用户可能点击的链接,提前预取。
  • 减少页面中的域名数量,可以直接减少DNS的请求。

二、SPDY 和 HTTP2

因为请求带来的 TCP 三次握手的 1.5 RTT 延迟,Google 引入 SPDY,尝试解决HTTP的延迟和安全性(HTTP 明文方式)问题。不过,SPDY 促使了 HTTP2.0 的诞生后,自己也不再更新,逐步退出。

4-2

SPDY 基于 SSL 之上,轻松兼容 HTTP 新老版本。其优势如下:

  • 多路复用。一个 TCP 连接传输多个资源。减少 TCP 连接成本。
  • 不同资源,不同优先级。比如优先加载首屏。
  • Header 头压缩。减少传送的字节数。SPDY 对 Header 压缩率可高达 80%。

SPDY 开拓了 HTTP 新局面,秒杀我们太多的前端优化工作,从本质上提升了页面加载速度。但我们前端优化的工作还是不能偏废。向着继续减少请求减少 TCP 连接建立的路上,让我们继续。

  • 合并资源,如 combo 合并 JavaScript 文件、CSS 文件,利用 sprite 合并图片,图片地图等;
  • 当页面资源较小时,可直接放页面中,如小图可使用 Base64 编码格式引入。甚至一些基础样式,或首屏依赖样式,都可以放在页面中;
  • 资源压缩技术。如 Gzip 等。主要是对响应数据的压缩~
  • 精简 JavaScript 和 CSS 代码。减少无用的空格。压缩混淆~
  • 避免链接重定向、避免错误的链接请求。建立多次链接、多次 DNS 解析,阻碍 DNS 预取技术。及时更新掉你页面中没有价值的链接吧。

三、资源加载

域名解析完,TCP 连接也建立起来后,资源加载器就开始工作了。

资源及资源加载器

资源包括:HTML、JavaScript、CSS 样式表、图片、SVG、字体文件、视频音频等。资源加载器有三种:

  1. 特定加载器,只加载某一种。如ImageLoader类。
  2. 缓存机制的资源加载器。特定加载器通过它查找是否有缓存资源,属于 HTML 的文档对象。
  3. 通用的资源加载器。在WebKit需要从网络或文件系统获取资源时使用。只负责获取资源的数据,被所有特定资源加载器共享。
资源加载的过程

在 WebKit 中,资源都以 CachedResource 为基类,以 Cached 为前缀,体现了浏览器的缓存机制。即请求资源时,浏览器会先看缓存中有没有这个资源,然后再决定是否向服务器发出请求。

5-11

这引出两个问题,首先,缓存资源的生命周期
浏览器缓存不会无限增大,缓存池中的数据必然出现更替,WebKit 采用 LRU 最近最少使用算法更新缓存池数据。WebKit 遵循 HTTP 协议,当页面刷新时,判断资源是否在资源池。若存在,则附上该资源在本地的一些信息(如修改时间等),发送 HTTP 请求给服务器,服务器根据信息作出判断,若资源没更新则网络状态为 304,利用现有资源;否则执行资源加载过程。

61

其次,资源加载过程
资源池中没有该资源时,执行加载过程。WebKit 可以并行(多线程)下载普通资源和 JavaScript 资源。在当前主线程被阻塞时,WebKit 会启动另一个线程去遍历后边的网页,收集需要的资源 URL再发请求,避免阻塞。

基于资源加载,前端优化建议:

  • 利用缓存机制,缓存常用且短时期内不会变更的资源,或给资源设置过期时间。

比如设置 ETag/Last-ModifiedExpires/Cache-Control
Expires/Cache-Control 两者作用一致,指明资源有效期,如果本地缓存还在有效期内,浏览器直接使用本地缓存,不再发送请求。两者同时配置时,Cache-Control 高于 Expires。

配置 ETag/Last-Modified 后,浏览器再次访问 URL 时,还会向服务器发送请求,确认文件是否已修改,没修改则服务器返回304,浏览器直接从本地缓存获取数据;修改过则服务器返回数据给浏览器。两者同时配置,服务器会优先检测 ETag,一致才会继续检测 Last-Modified。两者同时配置,可以使服务器更准确的判断浏览器是否已有需要的缓存数据。

ETag/Last-Modified 和 Expires/Cache-Control 两对都设置时, Expires/Cache-Control 优先级更高。所以,只要本地缓存在有效期内,就不会发送请求。但页面 F5 刷新和强刷时,缓存将失效。

71

  • 鉴于资源下载中可能被阻塞,将 JavaScript 文件放置页面下方。JavaScript 资源就是阻塞主线程的那个,而重建一个线程也是需要时间滴,所以把 JavaScript 扔最后吧~ 但 JavaScript 资源并不影响之前资源的加载和 DOM 树的构建。

四、从 URL 到 DOM 树的构建

当我们拿到页面所需的资源后,渲染引擎便启动 HTML 解释器,对获取的资源进行解析处理。网页代码(字节流)经过词法分析器解码,再由语法分析器解释成词语 Token,并构建成节点 Node,直到最终构建成一棵 DOM 树。

期间,当节点为 JavaScript 节点时,将启动 JavaScript 引擎,这时将阻塞 DOM 树的构建。因为 JavaScript 执行过程中, JavaScript 很可能会对 DOM 树进行读写操作。直到 JavaScript 执行完毕, DOM 树才会恢复构建。

其他资源并不影响 DOM 树的构建。

8-11

前端优化中,建议将 CSS 文件放在页首,以便构建 DOM 树;而将 JavaScript 文件尽量放在页面下方,防止阻塞构建 DOM 树;而 JavaScript 的 onload 事件里,不要写太多影响首屏渲染的、操作 DOM 树的 JavaScript 代码。

另外强调一下:
DOMContentLoaded: DOM 树构建完;
DOM 的onload事件: DOM 树构建完且网页依赖的资源都加载完了~

五、网页排版过程:由 DOM 树到构建 RenderLayer 树

这一过程,就像是页面的排版过程。它通过 CSS 样式信息,对 DOM 树进行排版,形成 RenderObject 树及 RenderLayer 树。

89-1

在 DOM 树构建完成后,WebKit 为 DOM 树节点构建 RenderObject 对象。WebKit 将根据盒模型计算节点的位置、大小等样式信息(即布局计算或排版),并将这些信息保存到对应的 RenderObject 对象。

1. CSS解释器

CSS解释过程,是从 CSS 字符串经过 CSS 解释器(CSSParser、CSSGrammer)处理后,变成渲染引擎的内部样式规则表示的过程。样式规则是解释器的输出结构,是样式匹配的输入数据。

具体过程:WebKit 在渲染元素时,CSS 解释器获取样式信息,返回匹配好的结果样式信息。每个元素可能需要匹配不同来源的规则,依次是用户代理(浏览器)规则集合、用户规则集合和HTML页面中包含的自定义规则集合。三者匹配方式类似。

对于每个规则集合,先查找 ID 规则,检查有无匹配的规则,然后依次检查类型规则、标签规则等。匹配好的规则,保存到匹配结果中。WebKit 对这些规则进行排序。对于元素需要的样式属性,WebKit 选择从高优先级规则中选取,并将样式属性值返回。

2. 渲染基础:RenderObject 树

DOM 树经过布局计算、CSS parse 后,将样式信息存储在 RenderObject 对象中,并构建成 RenderObject 树。同时,WebKit 会根据网页的层次结构创建 RenderLayer 树,完成绘图上下文。DOM 树、Render 树和绘图上下文同时并存,直到页面销毁。

RenderObject 树,基于 DOM 树的一棵新树,是布局计算和渲染等机制的基础设施。

DOM 节点建立新的 RenderObject 对象的时机:

  • DOM 树的 Document 节点。
  • DOM 树的可视节点,如html、body、div 等。非可视节点如meta、head、script 等不创建。
  • 为满足 WebKit 处理,需要建立匿名 RenderObject 节点,它不对应于 DOM 树的任何节点。如:匿名的 RenderBlock 节点。

DOM 树的每个节点对象会递归检查是否需要创建 RenderObject,并根据 DOM 节点类型创建 RenderObject 节点;动态加入的 DOM 元素,会相应的创建 RenderObject 节点。所有这些节点构成一棵 RenderObject 树。

55-1

3. 渲染基础:网页层次和 RenderLayer 树

在 HTML 页面上,网页分层展示。目的有两个:1. 方便开发网页、设置网页的层次;2. 简化 WebKit 渲染的逻辑。

在RenderObject 树基础上,WebKit 根据需要为其中的某些节点创建新的 RenderLayer 节点,并形成一棵 RenderLayer 树。

RenderObject 节点建立新 RenderLayer 对象的时机:

  • DOM 树的 Document 节点对应的 RenderView 节点。
  • DOM 树的 Document 的子节点,即 HTML 节点对应的 RenderBlock 节点。
  • 显式的指定 CSS 位置的 RenderObject 节点。
  • 有透明效果的 RenderObject 节点。
  • 节点有溢出 overflow、alpha 或反射等效果的 RenderObject 节点。
  • 使用Canvas 2D、3D (WebGL)技术的 RenderObject 节点。
  • Video 节点对应的 RenderObject 节点。

RenderLayer 节点的使用可以有效减少网页结构的复杂程度,并在许多情况下能减少重新渲染的开销。

4. 布局计算及重绘时机

CSS 盒模型,是布局计算的基础;渲染引擎用来确定如何排版元素、及元素间的位置关系。

44-1

布局计算,是针对 RenderObject 树及其子树的计算,是一种递归计算,其节点信息需要先计算其子节点的位置、大小等信息。RenderObject 对象会将计算结果存储,等待渲染时机。

  • 每个元素会实现自己的 layout。
  • 页面元素定义了宽高,则按自定义宽高确定元素大小。
  • 文本节点等内联元素,需要结合字号大小、文字多少确定宽高。
  • 页面元素确定的宽高超过了布局容器包含块提供的宽高,同时 overflow 为 visible 或 auto,WebKit 则提供滚动条保证可显示所有内容。
  • 一般页面元素的宽高是在布局时通过计算得来。除非网页定义了页面元素的宽高。

重绘时机:只要样式发生变化,就重新计算。

  • 首次打开页面,浏览器设置网页的可视区域,并调用计算布局的方法。可视区域改变时,网页包含块的大小也会改变,WebKit 需要重新计算布局。
  • 网页的动画会触发布局计算。动画可能改变样式属性。
  • JavaScript 通过 CSSOM(CSS 对象模型) 直接修改样式,会触发 WebKit 重新计算布局。
  • 用户交互,如滚动网页。

前端优化建议,因布局计算耗时间,一旦布局发生变化,WebKit 就需要后面的重新绘制操作。SO,减少样式的变动~减少重绘~利用 CSS3 新功能(如 CSS3 变形 translate、scale、rotate 等方法,过渡 transition 方法等)可有效提高网页的渲染效率。

六、 网页渲染过程:由 RenderLayer 树到最终的图像

在上一个过程,网页完成了 DOM 树到 RenderLayer 树的布局计算和排版处理。接下来,由渲染引擎(一般是绘图类工具)完成对 RenderLayer 树的绘制,并最终形成图像,展示给用户。

5555-1

1. 绘图上下文

绘图上下文,所有的绘图操作都是在该上下文中进行的。它是一个与平台无关的抽象类,它将每个绘图操作桥接到不同的绘图具体实现类。

2D 绘图上下文:

  • 提供基本绘图单元的绘制接口及设置绘图的样式。
  • 绘图接口包括:画点、画线、画图、画多边形、画文字等。绘图样式包括颜色、线宽、字号、渐变等。
  • CPU 来完成 2D 操作。或用 3D 图形接口( OpenGL )完成。

3D 绘图上下文:支持 CSS3D、WebGL 等。

  • 使用 3D 图形接口(OpenGL、Direct3D 等)
2. 渲染方式

软件渲染:CPU。通常渲染的结果是一个位图,绘制每一层时都使用该位图,区别在于位置可能不同,每一层按从后到前的顺序。没必要为每层分配一个位图,没必要合成。

缺点:对 HTML5 新技术,

  • 能力不足,CSS3D、WebGL;
  • 性能不好,如视频、Canvas 2D;
  • 使用率下降,特别是移动端。

优势:对更新区域处理,软件渲染可能只需要计算极小区域,硬件则需要绘制其中一层或多层,再合成。硬件代价大。

硬件加速渲染:GPU 必须有合成的步骤。分层绘制+合成。不过对于更新区域,如果只是在一个层,硬件可能会更快。

WebKit 的实现方式:

  • 使用合适的网页分层技术、减少重新计算的布局和绘图。
  • 使用CSS 3D 变形和动画技术。CSS 3D 变形技术,能让浏览器仅使用合成器合成所有层就可以达到动画效果。不需要布局计算和重绘~

前端优化建议:

  • 减少重绘:因为重绘是要计算布局、绘图、合成三个阶段。其中计算布局和绘图比较费时,合成要少

七、总结

至此,从输入 URL 到页面呈现,我们大致做了介绍。但这只是皮毛最上方的一点,更多浏览器内核的实质,值得我们下载一份源码,编译解析深挖~ 相信在前端优化的路上,知其然,知其所以然~ 定会走得跟远~~

八、参考资料

朱永盛 《WebKit技术内幕》
HTTP协议
WebKit源码

原文:http://jdc.jd.com/archives/2806


关注我

我的微信公众号:前端开发博客,在后台回复以下关键字可以获取资源。

  • 回复「小抄」,领取Vue、JavaScript 和 WebComponent 小抄 PDF
  • 回复「Vue脑图」获取 Vue 相关脑图
  • 回复「思维图」获取 JavaScript 相关思维图
  • 回复「简历」获取简历制作建议
  • 回复「简历模板」获取精选的简历模板
  • 回复「加群」进入500人前端精英群
  • 回复「电子书」下载我整理的大量前端资源,含面试、Vue实战项目、CSS和JavaScript电子书等。
  • 回复「知识点」下载高清JavaScript知识点图谱

每日分享有用的前端开发知识,加我微信:caibaojian89 交流