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 引擎(解释器)。
- 用户界面
用户界面就是用户肉眼可见的浏览器界面,包括浏览器的工具栏、地址栏、标签页、网页页面等。当我们通过鼠标或者键盘在用户界面上执行一些操作时,这些动作的本质是在向浏览器下达一个个“执行命令”。而用户界面的作用就是将用户的交互动作转换为命令,然后将这些命令交给浏览器引擎处理。
- 浏览器引擎
浏览器引擎接收到用户界面传来的命令后,它可以读懂这些命令的内容。虽然浏览器引擎认识命令,但它不会执行命令,它的作用依然是“转换命令” ——— 将命令转换为渲染引擎能理解的方式并将其传递。可以看出,用户界面和浏览器引擎只负责解析命令,但不会执行命令,它们都相当于是浏览器操作的“翻译员”和“搬运工”。
最终,经过两层的转换和翻译,命令终于被传到了渲染引擎的手中并开始执行。
- 渲染引擎
渲染引擎的工作就是将静态资源渲染为可视化界面,或者在用户操作后将页面及时更新并渲染。可以说,“渲染页面”就是渲染引擎的主要职责。渲染引擎可以识别我们的 HTML、CSS 代码,并利用这些代码信息计算页面布局,最终将代码变成丰富多样的页面显示在浏览器窗口上。
除此之外,渲染引擎还可以调度其他的工作模块。假设用户点击了一个按钮,该按钮点击后会异步请求一个文本资源,并将其存储在 localStorage 中,此时的渲染引擎并没有执行页面渲染的操作,却分别调用了网络模块(Networking)和数据存储(Data Persistence)的功能。这表示渲染引擎不仅可以执行 UI 更新的操作,也可以执行其他非页面变化的逻辑任务。
因为渲染引擎是整个浏览器的核心,因此人们把渲染引擎也看作是“浏览器内核”。
- 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 个阶段,每个阶段内容如下。
- HTML 解析
该阶段会解析 HTML 文档。在解析过程如果遇到文档中链接的各种外部资源(如 CSS 文件、JavaScript 文件、图片等),会对这些资源发起请求并加载。解析结束后会生成 DOM 树。
- CSS 解析
该阶段会识别并加载所有的 CSS 样式信息,解析后会生成 CSSOM 树。CSSOM 树也是一个树形结构,表示 DOM 树的各个节点对应的样式。
- 结构样式合并
该阶段会将前两步生成的 DOM 树和 CSSOM 树合并,组成一颗包含 HTML 结构和 CSS 样式的渲染树(Render Tree),这颗渲染树包含了页面中所有节点的结构和样式信息。
- 布局
渲染树已经将页面结构和样式表示完整。如果要显示页面,还必须知道每个元素应该放在浏览器窗口的哪个位置上、占据多大的空间。而这些有关元素的位置、大小等信息,就是该阶段需要完成的页面布局任务。
当页面布局完成,布局信息会被写回渲染树,形成了“布局渲染树”。到这一步为止,所有的计算都还是在内存中执行,用户不可见。接下来我们就可以执行最后一步 ——— 绘制页面了。
- 绘制
将内存中的布局渲染树绘制成一帧一帧的像素,在浏览器窗口中显示出来。这一步完成后用户才可以看到最终的目标页面。
7.1.3 重排与重绘
在 7.1.2 节中我们介绍了渲染引擎的工作原理,了解了一个 HTML 文件如何被渲染成一张网页,这个渲染过程(生命周期)发生在页面第一次被加载的时候。在页面初始化完成后,我们还可能会通过 CSS、JS 来对页面中的元素进行修改,这些修改会重新触发一部分页面渲染的生命周期。
重走页面生命周期的这个过程,有两种主要的形式 ——— 重排与重绘。
- 重排
当我们修改了某个元素的大小或者位置(比如修改元素的宽高、内外边距,或隐藏元素等)时,浏览器需要重新计算该元素的大小和位置,同时其他元素的大小和位置可能也会受到影响。此时浏览器需要对页面的元素重新排列,这个过程就是重排(也叫回流)。
因为重排会对多个元素产生影响,所以需要重新布局,其生命周期如图 8-3 所示。
我们以下面这段基础的 HTML 代码片段为例:
<div id="target">
<span id="targetText">我是一个小测试</span>
</div>
<!-- 样式 -->
<style>
#target {
width: 100px;
height: 100px;
}
</style>
当页面初次渲染完成后,在 JavaScript 中执行以下操作,都会触发页面的重排:
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 等。
总结重排的触发场景:当修改元素的几何属性、修改元素结构、或访问需要即时计算的属性时,就会触发页面的重排。
- 重绘
当我们修改了元素的显示样式(如字体颜色、背景色、阴影、圆角等)时,元素的几何属性(位置或大小)没有发生变化,也不会导致其他的元素发生变化,此时浏览器便可以跳过页面布局的步骤,直接为该元素绘制新的样式,这个过程叫重绘。
很显然,重绘的性能要比重排好的多。因为它只需要修改目标元素的样式即可,不会产生计算或影响其他元素,从而避免了大面积的元素重排。
var dom = document.getElementById("target");
// 修改以下属性触发重绘
dom.style.color = "blue";
dom.style.background = "red";
dom.style.boxShadow = "1px 1px 2px 2px #555";
当页面发生重绘时,其过程如图 8-4 所示。
总结重绘的触发场景:当修改元素的显示属性,不涉及任何布局计算时,就会触发页面的重绘。