看啥推荐读物
专栏名称: 黑夜ya
做些自己想做的事情
今天看啥  ›  专栏  ›  黑夜ya

浅析浏览器渲染原理

黑夜ya  · 掘金  ·  · 2020-01-20 10:20
阅读 29

浅析浏览器渲染原理

一、前言

对于每一个前端工程师来说,了解浏览器的工作原理会大大帮助我们更加深刻地认识浏览器如何将 HTML 文本渲染到浏览器界面上。这也能够帮助我们写出高性能的代码。

目前使用的主流浏览器有:IE(令人头疼),Firefox,Safari,Chrome,Opera 浏览器。

浏览器的主要功能就是向服务器发出请求,在浏览器窗口中展示获取到的资源。这里的资源不仅包括 HTML 文档,也可以是图片、视频或其他类型。资源的位置由用户使用 URI(统一资源标识符)指定的。

而浏览器如何渲染界面,是由 HTML(超文本标记语言)和 CSS(层叠样式表)共同决定的。浏览器通过解析 HTML 和 CSS 文件渲染出界面,并且,我们还可以使用 Javascript 语言与浏览器进行交互,调用浏览器提供的 API、操作 DOM 树或者改变元素的 CSS 样式,以触发页面的重新渲染。

二、浏览器的主要组件

浏览器包含了以下组件:

  • 用户界面:包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口(显示页面),其他部分都属于用户界面。
  • 浏览器引擎:在用户界面和呈现引擎之间传送指令。
  • 渲染引擎:显示(渲染)请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
  • 网络请求:用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现。
  • 用户界面后端:用于绘制基本的窗口小部件,比如组合框和窗口。公开了与平台无关的通用接口,在底层使用操作系统的用户界面方法。
  • Javascript 解释器:用于解析和执行 JavaScript 代码。
  • 数据存储:浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。

三、多进程架构

3.1 进程与线程

进程就像是一个有边界的生产厂间,而线程就像是厂间内的一个个员工,可以自己做自己的事情,也可以相互配合做同一件事情。

进程和线程都是一个时间段的描述,是 CPU 工作时间段的描述,不过是颗粒大小不同。

进程之间是不共享资源和地址空间的,而多个线程,是共享着相同的资源和空间的。线程是依附于进程的,而进程中使用多线程并行处理提高运算效率。

3.2 浏览器多进程

正如上图所示,浏览器是一个多进程架构。最新的 Chrome 浏览器包含了:1 个浏览器主进程,1 个 GPU 进程,1 个网络进程,多个渲染进程和插件进程。

  • 浏览器主进程。主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
  • 渲染进程。核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
  • GPU 进程。而 GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘。
  • 网络进程。主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
  • 插件进程。主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。

多进程结构提升了浏览器的稳定性、流畅性、安全性之外也带来了更高的内存占用。

四、渲染原理

4.1 渲染引擎

浏览器的内核是分为两个部分的,一是渲染引擎,另一个是 JS 引擎。现在 JS 引擎比较独立,内核更加倾向于说渲染引擎。渲染引擎负责渲染,即渲染 HTML 文档或者图片等。常见渲染引擎如下:

  • Trident:代表是 IE,因 IE 捆绑在 Windows 系统中,所以占有极高的份额,又被称为 IE 内核或 MSHTML,此内核只能用于 Windows 平台,且不是开源的。
  • Gecko:代表是 Firefox,其最大的优势是跨平台。
  • Webkit:代表是 safiri,较早版本的 chrome。
  • Presto:代表作品是 Opera,但已经被 Opera 弃用。
  • Blink:由 Google 和 Opera Software 开发的浏览器排版引擎,2013 年 4 月发布。现在 Chrome 内核是 Blink。谷歌还开发了自己的 JS 引擎,V8,使 JS 运行速度极大地提高了。

4.2 页面加载过程

在介绍浏览器渲染过程之前,先了解下从输入 URL 到浏览器获取到服务器资源的过程。主要经过以下步骤:

  1. 输入 URL 后,首先会根据 DNS 查询,返回真实的 IP 地址。
  2. 接下来是 TCP 握手,应用层会下发数据给传输层,这里 TCP 协议会指明两端的端口号,然后下发给网络层。网络层中的 IP 协议会确定 IP 地址,并且指示了数据传输中如何跳转路由器。然后包会再被封装到数据链路层的数据帧结构中,最后就是物理层面的传输了。
  3. TCP 握手结束后会进行 TLS 握手,然后就开始正式的传输数据。
  4. 服务器拿到解析后的 HTTP 请求后,处理并返回 HTTP 响应。假设返回一个 HTML 文档。
  5. 浏览器拿到解析后的 HTTP 响应后,判断状态码,如果是 200 则进入浏览器渲染过程,如果是 400 或 500 就会报错,根据相应的状态码作出处理。
  6. 至此,浏览器拿到响应结果后,就开始渲染过程。

4.3 渲染过程

browser-渲染过程-2020-1-17.png

图:Webkit 渲染过程

如上图所示,浏览器的渲染机制大体可以分为以下几个步骤:

  1. 处理 HTML 并构建 DOM 树。
  2. 处理 CSS 构建 CSSOM 树。
  3. 将 DOM 与 CSSOM 合并成一个渲染树。
  4. 根据渲染树来布局,计算每个节点的位置。
  5. 调用 GPU 绘制,合成图层,显示在屏幕上。

4.2.1 构建 DOM 树

假设服务器返回如下 HTML 结构:

<!DOCTYPE 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="awesome-photo.jpg" /></div>
  </body>
</html>
复制代码

浏览器拿到该 HTML 文档后,会经过如下几个步骤构建 DOM 树。

browser-解析HTML-2020-1-14.png

  1. 转换

浏览器从磁盘或网络读取 HTML 的原始字节,并根据文件的指定编码(例如 UTF-8)将它们转换成各个字符。

  1. Token 令牌化

浏览器将字符串转换成 W3C HTML5 标准规定的各种令牌,例如,“<html>”、“<body>”,以及其他尖括号内的字符串。每个令牌都具有特殊含义和一组规则。

  1. 词法解析

发出的令牌转换成定义其属性和规则的对象

  1. DOM 树

浏览器根据 HTML 标记之间的关系,创建如下类似树结构的对象模型。

browser-DOM树-2020-1-14.png

最终输出是我们这个简单页面的文档对象模型(DOM),后续进一步处理会用到它。

对于大型的 HTML 文档,浏览器需要处理大量的 HTML 标记,这很容易成为页面渲染的瓶颈。

4.2.2 构建 CSSOM 树

浏览器遇到前面例子中头部的<link>标记,该标记引用了一个外部的 css 样式表:style.css。它会立即发起请求,并且得到结果。假设返回如下的内容:

body {
  font-size: 16px;
}
p {
  font-weight: bold;
}
span {
  color: red;
}
p span {
  display: none;
}
img {
  float: right;
}
复制代码

与处理 HTML 文档一样,我们需要将收到的 CSS 规则转换成某种浏览器能够理解和处理的东西。

Bytes -> Characters -> Tokens -> Nodes -> CSSOM
复制代码

CSS 字节转换成字符,接着转换成令牌和节点,最后链接到一个称为CSS 对象模型(CSSOM) 的树结构。

browser-CSSOM树-2020-1-14.png

4.2.3 构建渲染树

我们已经通过解析 HTML 和 CSS 构建了 DOM 树和 CSSOM 树,DOM 树描述内容,CSSOM 树则描述 DOM 树中相应节点的样式规则。接下来,就需要将 DOM 和 CSSOM 合并成渲染树(Render Tree)

browser-合成渲染树-2020-1-17.png

为构建渲染树,浏览器大体上完成了下列工作:

  1. 从 DOM 树的根节点开始遍历每个可见节点。
  • 某些节点不可见(例如脚本标记、元标记等),因为它们不会体现在渲染输出中,所以会被忽略。

  • 某些节点通过 CSS 隐藏,因此在渲染树中也会被忽略,例如,某些应用了display: none规则的节点。

  1. 对于每个可见节点,为其找到适配的 CSSOM 规则并应用它们。

  2. 渲染可见节点,包括其内容和计算的样式。

4.2.4 布局与绘制

有了渲染树,就可以进入到布局阶段。布局流程的输出是一个盒模型,它会精确地捕获每个元素在视口内的确切位置和尺寸,所有相对测量值都转换为屏幕上的绝对像素。

布局完成后,浏览器会立即发出“Paint Setup”和“Paint”事件,将渲染树转换成屏幕上的像素。

4.4 阻塞渲染

在浏览器拿到 HTML、CSS、JS 等外部资源到渲染出页面的过程中,每一个步骤如果耗费时间长,就会影响页面的绘制,导致页面长时间的空白。毫无疑问,这种体验是非常差劲的。

  1. CSS 加载不会阻塞 DOM 树解析,但是会阻塞 DOM 树渲染
<link href="style.css" rel="stylesheet" />
复制代码

浏览器在遇到<link>标签引用的 CSS 外部资源时,会去请求该资源,然后继续进行 DOM 的解析。因为 CSS 不会对 DOM 树的解析有影响,所以浏览器可以继续解析 DOM。但是,在渲染的时候,由于依赖于 CSS 的样式规则,需要计算节点的样式、布局等,因此,浏览器出于性能的考虑,尽可能减少渲染次数,CSS 就会阻塞页面渲染。

  1. 默认情况下,JS 会阻塞 DOM 解析
<script src="script.js"></script>
复制代码

上面是一个常见的引入 JS 文件的例子,我们经常会把<script>脚本放到<body>的最下方。这么做的原因在于,浏览器不知道脚本的内容是什么。脚本可以随意操作 DOM,因此浏览器遇到脚本时,就会停止解析,执行脚本后再继续解析 DOM。

但是,我们也可以使用defer属性。让脚本的执行延迟到所有元素解析完成之后,DOMContentLoaded 事件触发之前。

4.5 重绘和回流

重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式。

回流:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来,这个过程就是回流(也叫重排)。

回流比重绘的代价要更高。

  1. 常见引起回流属性和方法
  • 添加或者删除可见的 DOM 元素;

  • 元素尺寸改变——边距、填充、边框、宽度和高度;

  • 内容变化,比如用户在 input 框中输入文字;

  • 浏览器窗口尺寸改变——resize事件发生时;

  • 计算 offsetWidthoffsetHeight 属性;

  • 设置 style 属性的值。

  1. 常见引起重绘属性与方法
  • clientWidth、clientHeight、clientTop、clientLeft

  • offsetWidth、offsetHeight、offsetTop、offsetLeft

  • scrollWidth、scrollHeight、scrollTop、scrollLeft

  • width、height

  • getComputedStyle()

  • getBoundingClientRect()

结语

认识浏览器渲染原理,我们就可以针对浏览器做一些性能优化,比如预加载、预解析、懒加载、资源压缩等等。这有利于我们更好地提高页面渲染的速度,增加用户的体验。

参考链接

  1. 浏览器的工作原理:新式网络浏览器幕后揭秘
  2. Inside look at modern web browser



原文地址:访问原文地址
快照地址: 访问文章快照