Skip to content

7.1 浏览器简介

浏览器(英语:Web Browser,全称为网页浏览器)是一种用于检索并展示 Web 信息资源的应用程序。这些信息资源可为网页、图片、影音或其他内容,它们由统一资源标志符(URI)标识。信息资源中的超链接可使用户方便地切换并浏览其他网页的信息。浏览器是 Web 网页的载体,是查找网上资源的入口和途径。

浏览器是一个非常复杂的应用程序。对于前端开发者来说,浏览器的工作是根据不同的 IP 和端口,查找并解析 HTML 内容,并将 HTML 中编写的代码渲染成可视的网页。如果网页需要交互,浏览器还要执行 JavaScript 脚本,这是现代浏览器的核心。当然使用浏览器检索资源时还离不开网络、搜索引擎、数据存储等,看似简单的搜索和打开网页,其实中间经历了非常复杂的过程。

深入了解浏览器这个庞然大物的最好方式就是将其拆分开来。我们把浏览器的每个组成部分看成一个独立单元,详细了解每一个单元的功能和作用是什么,进而了解它的工作原理和运行机制。

7.1.1 浏览器组成

浏览器的组成基本上可以分为几大结构,我们从如图 8-1 所示的结构图开始。

上图中将浏览器结构分为了 8 个部分,每个部分对应的含义如下:

  • User Interface:用户界面。
  • Browser Engine:浏览器引擎。
  • Rendering Engine:渲染引擎。
  • JavaScript Interpreter:JavaScript 解释器。
  • Networking:网络模块。
  • XML Parser:XML 解析器。
  • Display Backend:UI 后台。
  • Data Persistence:数据存储。

上面列出的每个部分都负责浏览器中的一块独立的内容。比如:Networking 负责整个浏览器的资源请求;XML Parser 用于将 XML 文档解析成 DOM 树;Data Persistence 负责浏览器中的所有数据存储,如 localStorage、sessionStorage、cookie 等。

为了更好的理解浏览器的工作原理,我们从上述的 8 个模块中选择最核心的 4 个模块详细介绍,这 4 个模块按照执行顺序依次是:用户界面、浏览器引擎、渲染引擎、JavaScript 引擎(解释器)。

  1. 用户界面

用户界面就是用户肉眼可见的浏览器界面,包括浏览器的工具栏、地址栏、标签页、网页页面等。当我们通过鼠标或者键盘在用户界面上执行一些操作时,这些动作的本质是在向浏览器下达一个个“执行命令”。而用户界面的作用就是将用户的交互动作转换为命令,然后将这些命令交给浏览器引擎处理。

  1. 浏览器引擎

浏览器引擎接收到用户界面传来的命令后,它可以读懂这些命令的内容。虽然浏览器引擎认识命令,但它不会执行命令,它的作用依然是“转换命令” ——— 将命令转换为渲染引擎能理解的方式并将其传递。可以看出,用户界面和浏览器引擎只负责解析命令,但不会执行命令,它们都相当于是浏览器操作的“翻译员”和“搬运工”。

最终,经过两层的转换和翻译,命令终于被传到了渲染引擎的手中并开始执行。

  1. 渲染引擎

渲染引擎的工作就是将静态资源渲染为可视化界面,或者在用户操作后将页面及时更新并渲染。可以说,“渲染页面”就是渲染引擎的主要职责。渲染引擎可以识别我们的 HTML、CSS 代码,并利用这些代码信息计算页面布局,最终将代码变成丰富多样的页面显示在浏览器窗口上。

除此之外,渲染引擎还可以调度其他的工作模块。假设用户点击了一个按钮,该按钮点击后会异步请求一个文本资源,并将其存储在 localStorage 中,此时的渲染引擎并没有执行页面渲染的操作,却分别调用了网络模块(Networking)和数据存储(Data Persistence)的功能。这表示渲染引擎不仅可以执行 UI 更新的操作,也可以执行其他非页面变化的逻辑任务。

因为渲染引擎是整个浏览器的核心,因此人们把渲染引擎也看作是“浏览器内核”。

  1. JavaScript 引擎

JavaScript 是一种解释型的语言,其解释器被称作 “JavaScript 引擎”。JavaScript 引擎的作用就是执行 JavaScript 代码。其原理主要包含如下 4 个步骤:

(1)将 JavaScript 代码拆分为最小的单个字符。 (2)将字符转换为抽象语法树(AST)。 (3)将抽象语法树转为 JavaScript 引擎可以执行的二进制代码。 (4)执行生成的二进制代码。

在浏览器中,JavaScript 引擎和渲染引擎需要互相使用对方的能力。当渲染引擎遇到 JavaScript 代码时,会将代码交给 JavaScript 引擎来执行并获取结果;当 JavaScript 引擎需要访问 DOM 树时,又需要渲染引擎来提供访问 DOM 的能力。因此在渲染引擎执行任务的过程中,需要频繁地与 JavaScript 引擎进行交互。

7.1.2 渲染引擎工作原理

因为渲染引擎是浏览器最核心最关键的模块,因此它也被称为浏览器内核。市面上有多款不同的浏览器,它们之间的主要差别也在于渲染引擎的差别。根据主流浏览器的分类,目前常见的浏览器内核有以下几种:

  • Trident(IE)
  • Gecko(火狐)
  • Blink(Chrome、Opera)
  • Webkit(Safari)。

Webkit 应该是大家最熟悉的,之前它也是 Chrome 的内核。不过后来由于 Chrome 内部改造,基于 Webkit 实现了 Blink,但 Blink 依然保留了 Webkit 绝大部分的特性。当前国内最常用的浏览器只有 Chrome 和 Safari(像 QQ 浏览器、360 浏览器等一众国产浏览器都是 Chrome 内核,将其看作 Chrome 即可),因此了解浏览器的渲染引擎工作原理,我们直接从 Webkit 入手。

在 7.1.1 节中我们介绍了渲染引擎可以将静态资源转化为网页页面。以 Webkit 为例,其具体的工作步骤如图 8-2 所示。

从上图中可以看出,渲染引擎的执行过程可以分为 5 个阶段,每个阶段内容如下。

  1. HTML 解析

该阶段会解析 HTML 文档。在解析过程如果遇到文档中链接的各种外部资源(如 CSS 文件、JavaScript 文件、图片等),会对这些资源发起请求并加载。解析结束后会生成 DOM 树。

  1. CSS 解析

该阶段会识别并加载所有的 CSS 样式信息,解析后会生成 CSSOM 树。CSSOM 树也是一个树形结构,表示 DOM 树的各个节点对应的样式。

  1. 结构样式合并

该阶段会将前两步生成的 DOM 树和 CSSOM 树合并,组成一颗包含 HTML 结构和 CSS 样式的渲染树(Render Tree),这颗渲染树包含了页面中所有节点的结构和样式信息。

  1. 布局

渲染树已经将页面结构和样式表示完整。如果要显示页面,还必须知道每个元素应该放在浏览器窗口的哪个位置上、占据多大的空间。而这些有关元素的位置、大小等信息,就是该阶段需要完成的页面布局任务。

当页面布局完成,布局信息会被写回渲染树,形成了“布局渲染树”。到这一步为止,所有的计算都还是在内存中执行,用户不可见。接下来我们就可以执行最后一步 ——— 绘制页面了。

  1. 绘制

将内存中的布局渲染树绘制成一帧一帧的像素,在浏览器窗口中显示出来。这一步完成后用户才可以看到最终的目标页面。

7.1.3 重排与重绘

在 7.1.2 节中我们介绍了渲染引擎的工作原理,了解了一个 HTML 文件如何被渲染成一张网页,这个渲染过程(生命周期)发生在页面第一次被加载的时候。在页面初始化完成后,我们还可能会通过 CSS、JS 来对页面中的元素进行修改,这些修改会重新触发一部分页面渲染的生命周期。

重走页面生命周期的这个过程,有两种主要的形式 ——— 重排与重绘。

  1. 重排

当我们修改了某个元素的大小或者位置(比如修改元素的宽高、内外边距,或隐藏元素等)时,浏览器需要重新计算该元素的大小和位置,同时其他元素的大小和位置可能也会受到影响。此时浏览器需要对页面的元素重新排列,这个过程就是重排(也叫回流)。

因为重排会对多个元素产生影响,所以需要重新布局,其生命周期如图 8-3 所示。

我们以下面这段基础的 HTML 代码片段为例:

html
<div id="target">
  <span id="targetText">我是一个小测试</span>
</div>
<!-- 样式 -->
<style>
  #target {
    width: 100px;
    height: 100px;
  }
</style>

当页面初次渲染完成后,在 JavaScript 中执行以下操作,都会触发页面的重排:

js
var dom = document.getElementById("target");
// 修改宽高
dom.style.width = "200px";
// 修改边距
dom.style.margin = "10px";
dom.style.padding = "10px";
// 修改元素显示
dom.style.display = "none";
dom.style.display = "flex";
// 添加/删除元素
dom.innerHTML = "<p>子元素</p>";
dom.remove();
// 访问需要即时计算的属性
let top = dom.offsetTop;
let style = dom.getComputedStyle();

需要注意的是,访问元素需要“即时计算”的属性时会发生重排。这是因为这些属性的值需要将元素重排才能计算并得到,而不是为了重新绘制页面。这些属性包括 offsetTop、offsetLeft、scrollTop、scrollLeft、clientTop、clientLeft 等。

总结重排的触发场景:当修改元素的几何属性、修改元素结构、或访问需要即时计算的属性时,就会触发页面的重排。

  1. 重绘

当我们修改了元素的显示样式(如字体颜色、背景色、阴影、圆角等)时,元素的几何属性(位置或大小)没有发生变化,也不会导致其他的元素发生变化,此时浏览器便可以跳过页面布局的步骤,直接为该元素绘制新的样式,这个过程叫重绘。

很显然,重绘的性能要比重排好的多。因为它只需要修改目标元素的样式即可,不会产生计算或影响其他元素,从而避免了大面积的元素重排。

js
var dom = document.getElementById("target");
// 修改以下属性触发重绘
dom.style.color = "blue";
dom.style.background = "red";
dom.style.boxShadow = "1px 1px 2px 2px #555";

当页面发生重绘时,其过程如图 8-4 所示。

总结重绘的触发场景:当修改元素的显示属性,不涉及任何布局计算时,就会触发页面的重绘。