toFrankie / blog

种一棵树,最好的时间是十年前。其次,是现在。
20 stars 1 forks source link

浏览器内核及渲染过程杂谈 #245

Open toFrankie opened 1 year ago

toFrankie commented 1 year ago

配图源自 Freepik

网页依赖于浏览器这个宿主环境,那么它是如何渲染,怎样显示在屏幕上的呢?

一、浏览器内核

浏览器主要由用户界面、浏览器内核、数据存储、网络等组成。而浏览器内核分为两部分:渲染引擎(Rendering Engine)和 JS 引擎(JavaScript Engine)。

渲染引擎常被称为“浏览器内核”,主要功能是解析 HTML、CSS 进行页面渲染,渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。渲染引擎也被称为排版引擎(Layout Engine)、浏览器引擎(Browser Engine)。而 JS 引擎负责 JavaScript 脚本的解析、编译和执行。

早期内核的概念并没有明确区分渲染引擎和 JS 引擎,现在 JS 引擎已经独立出来,而我们常说的浏览器内核通常指的是渲染引擎。我们知道 JavaScript 是 ECMAScript 标准的一种实现,JavaScript 在浏览器端实现还必须包括 DOM 和 BOM。

1.1 浏览器内核

下面列举一些常见的浏览器引擎

Timeline

苹果要求其平台下浏览器必须使用 WebKit 渲染引擎,所以一些浏览器在不同平台,其内核是不一样的。(查看当前浏览器内核:浏览器内核版本检测)。

1.2 JS 引擎

一些常见的 JS 引擎

1.3 浏览器引擎前缀

浏览器厂商们有时会给实验性的或者非标准的 CSS 属性和 JavaScript API 添加前缀,这样开发者就可以用这些新的特性进行试验。

CSS 前缀:

div {
  -webkit-transition: all 4s ease;
  -moz-transition: all 4s ease;
  -ms-transition: all 4s ease;
  -o-transition: all 4s ease;
  transition: all 4s ease;
}

API 前缀:

接口前缀,需要大写的前缀修饰接口名:

属性和方法前缀,需要使用小写的前缀修饰属性或者方法:

const requestAnimationFrame = window.requestAnimationFrame ||
                              window.mozRequestAnimationFrame ||
                              window.webkitRequestAnimationFrame ||
                              window.oRequestAnimationFrame ||
                              window.msRequestAnimationFrame

二、网页生成过程

首先,渲染引擎从网络层请求到 HTML 文档,然后进行如下所示的基本流程:

Rendering Engine Basic Flow

以 WebKit 为例,主流程如下:

WebKit Main Flow

至于 Mozilla 的 Gecko,整体流程跟 WebKit 基本相同,术语稍有不同。例如 WebKit 的 Layout(布局),在 Gecko 上称为 Reflow(重排)。由于本文并非两者的差异,因此不展开赘述,详情看 Introduction to Layout in Mozilla Overview

大致过程:

  1. 渲染引擎开始解析 HTML 文档,构建出 DOM Tree
  2. 同时解析外部 CSS 文件以及样式元素中的样式数据,构建出 CSSOM Tree,即上图的 Style Rules。
  3. 结合 DOM TreeCSSOM Tree,生成一个 Render Tree(渲染树)。
  4. 接着进入 Layout (布局)处理阶段,即将 Render Tree 的所有节点分配一个应出现在屏幕上的确切坐标。
  5. 下一个是 Painting (绘制)阶段,渲染引擎会遍历 Render Tree 将每个节点绘制出来。

需要着重指出的是,这是一个渐进的过程。为了达到更好的用户体验,渲染引擎会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建 Render Tree 和设置 Layout。在不断接收和处理来自网络的其余内容的同时,渲染引擎会将部分内容解析并显示出来。

其中 DOM 是 Document Object Model 的简写,而 CSSOM 则是 CSS Object Model 的简写。

需要注意的是:

Render TreeDOM Tree 是相对应的,但并不是一一对应的。非可视化的元素不会插入 Render Tree 中,如 HTML 的 head 元素。如果元素的 displaynone,那么也不会显示在 Render Tree 中,但是 visibilityhidden 的元素仍会显示。

三、解析

解析是渲染引擎中非常重要的一个环节。

3.1 什么是解析?

解析文档是指将文档转化为有意义的结构,也就是可让代码理解和使用的结构。解析得到的结果通常是代表了文档结构的节点树,它被称作“解析树”或“语法树”。

From source document to parse tree

解析分为两个过程:词法分析(Lexical Analysis)和语法分析(Syntax Analysis)。

词法分析是将输入内容分割成大量有效的标记(token)的过程。token 是语言词汇,是组成内容最小的元素。语法分析是指应用语言的语法规则的过程。

解析器是这样工作的:词法分析器(Lexer)负责将输入内容分割成一个个有效的 token;而解析器(Parser)负责根据语言的语法规则分析文档结构,从而构建出解析树(Parse Tree)。词法分析器知道如何将无关的字符(如空格、换行符等)分离出来。

举个例子,我们在 AST Explorer 网站将以下这段 HTML 文档解析成“树”结构。

<!DOCTYPE html>
<html>
  <body>
    <h1>Hello,</h1>
    <p id="name">I'm Frankie.</p>
  </body>
</html>

假设我们要访问 <p> 标签的 id 属性值,如果不将其先解析为“树”的话,应该会想到使用正则表达式去匹配源文档,若需要获取的属性各种各样,显然正则表达式会很麻烦。但现在,我们可以通过类似 document.html.body.p.attrs.id 的形式来获取其值。

如果你对浏览器如何将 HTML 生成“抽象语法树”(AST),可以看下这个库:parse5

3.2 翻译

很多时候,语法树(解析树)并不是最终的产品。解析通常在翻译过程中使用的,而翻译是指将输入文档转换成另一种格式。编译就是这样一个例子。编译器可以将源代码(source code)编译成机器代码(machine code)。

Compilation flow

四、处理脚本和样式表的顺序

4.1 脚本

网络的模型是同步的。网页作者希望解析器遇到 <script> 标记时立即解析并执行脚本。文档的解析将停止,直到脚本执行完毕。如果脚本是外部的,那么解析过程会停止,直到从网络同步抓取资源完成后再继续。此模型已经使用了多年,也在 HTML4 和 HTML5 规范中进行了指定。作者也可以将脚本标注为 defer,这样它就不会停止文档解析,而是等到解析结束才执行。HTML5 增加了一个选项,可将脚本标记为异步(async),以便由其他线程解析和执行。

4.2 预解析

WebKit 和 Gecko 都进行了这项优化。在执行脚本时,其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。通过这种方式,资源可以子在并行连接上加载,从而提高总体速度。

需要注意的是,预解析器不会修改 DOM Tree,而是将这项工作交由主解析器处理。预解析器只会解析外部资源(例如外部脚本、样式表和图片)的引用。

未完待续...