前言
配图源自 Freepik
网页依赖于浏览器这个宿主环境,那么它是如何渲染,怎样显示在屏幕上的呢?
正文
一、浏览器内核
浏览器主要由用户界面、浏览器内核、数据存储、网络等组成。而浏览器内核分为两部分:渲染引擎(Rendering Engine)和 JS 引擎(JavaScript Engine)。
渲染引擎常被称为“浏览器内核”,主要功能是解析 HTML、CSS 进行页面渲染,渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。渲染引擎也被称为排版引擎(Layout Engine)、浏览器引擎(Browser Engine)。而 JS 引擎负责 JavaScript 脚本的解析、编译和执行。
早期内核的概念并没有明确区分渲染引擎和 JS 引擎,现在 JS 引擎已经独立出来,而我们常说的浏览器内核通常指的是渲染引擎。我们知道 JavaScript 是 ECMAScript 标准的一种实现,JavaScript 在浏览器端实现还必须包括 DOM 和 BOM。
1.1 浏览器内核
下面列举一些常见的浏览器引擎:
- Chrome:早期使用 WebKit 引擎,自 2013 年起使用 Blink 引擎。Blink 派生自 WebKit 的一个分支。注意:iOS 版本的 Chrome 使用了 iOS 平台的渲染引擎,而非 Blink。
- FireFox:使用 Gecko 内核。
- Opera:早期采用 Elektra、Presto 引擎,自 2013 年起使用 Blink 引擎。
- Safari:采用 WebKit 引擎。WebKit 早期源自 KHTML 引擎。
- IE:采用 Trident 引擎,又称为 MSHTML。
- Edge:自 2015 年 1 月 25 日发布 Microsoft Edge 浏览器,其内核使用 EdgeHTML 引擎(EdgeHTML 是 Trident 的一个分支)。从 2020 年 1 月 15 日发布基于 Chromium 的 Edge 浏览器,使用 Blink 引擎。
Timeline
苹果要求其平台下浏览器必须使用 WebKit 渲染引擎,所以一些浏览器在不同平台,其内核是不一样的。(查看当前浏览器内核:浏览器内核版本检测)。
1.2 JS 引擎
一些常见的 JS 引擎:
- Chrome:采用 V8 引擎。V8 引擎除了在 Google Chrome、基于 Chromium 的浏览器中使用,也被使用于服务端,如 Node.js 等。
- FireFox:各版本所采用引擎的情况:SpiderMonkey(1.0 ~ 3.0)、TraceMonkey(3.5 ~ 3.6)、JägerMonkey(4.0 ~)。
- Opera:各版本所采用引擎的情况:Opera Linear A(4.0 ~ 6.1)、Linear B(7.0 ~ 9.2)、Futhark(9.5 ~ 10.2)、Carakan(10.5 ~)
- Safari:采用 JavaScriptCore 引擎。
- IE:在 IE3 ~ IE8 采用 JScript 引擎,IE9 ~ IE11 使用 Chakra 引擎。
- Edge:采用 Chakra 引擎,而基于 Chromium 的 Edge 浏览器则使用 V8 引擎。
1.3 浏览器引擎前缀
浏览器厂商们有时会给实验性的或者非标准的 CSS 属性和 JavaScript API 添加前缀,这样开发者就可以用这些新的特性进行试验。
CSS 前缀:
-webkit-
(Chrome、Safari、新版 Opera 浏览器,以及几乎所有 iOS 系统中的浏览器(包括 iOS 系统中的火狐浏览器);基本上所有基于 WebKit 内核的浏览器)。-moz-
(Firefox 浏览器)-o-
(旧版 Opera 浏览器)-ms-
(IE 浏览器和 旧版 Edge 浏览器)
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 前缀:
接口前缀,需要大写的前缀修饰接口名:
WebKit
(Chrome、Safari、新版 Opera 浏览器,以及几乎所有 iOS 系统中的浏览器(包括 iOS 系统中的火狐浏览器);基本上所有基于 WebKit 内核的浏览器)。MOZ
(Firefox 浏览器)O
(旧版 Opera 浏览器)MS
(IE 浏览器和 旧版 Edge 浏览器)
属性和方法前缀,需要使用小写的前缀修饰属性或者方法:
webkit
(Chrome、Safari、新版 Opera 浏览器,以及几乎所有 iOS 系统中的浏览器(包括 iOS 系统中的火狐浏览器);基本上所有基于 WebKit 内核的浏览器)。moz
(Firefox 浏览器)o
(旧版 Opera 浏览器)ms
(IE 浏览器和 旧版 Edge 浏览器)
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。
大致过程:
- 渲染引擎开始解析 HTML 文档,构建出
DOM Tree
。 - 同时解析外部 CSS 文件以及样式元素中的样式数据,构建出
CSSOM Tree
,即上图的 Style Rules。 - 结合
DOM Tree
和CSSOM Tree
,生成一个Render Tree
(渲染树)。 - 接着进入
Layout
(布局)处理阶段,即将Render Tree
的所有节点分配一个应出现在屏幕上的确切坐标。 - 下一个是
Painting
(绘制)阶段,渲染引擎会遍历Render Tree
将每个节点绘制出来。
需要着重指出的是,这是一个渐进的过程。为了达到更好的用户体验,渲染引擎会力求
尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建 Render Tree 和设置 Layout。在不断接收和处理来自网络的其余内容的同时,渲染引擎会将部分内容解析并显示出来。
其中
DOM
是 Document Object Model 的简写,而CSSOM
则是 CSS Object Model 的简写。
需要注意的是:
Render Tree
和 DOM Tree
是相对应的,但并不是一一对应的。非可视化的元素不会插入 Render Tree
中,如 HTML 的 head
元素。如果元素的 display
为 none
,那么也不会显示在 Render Tree
中,但是 visibility
为 hidden
的元素仍会显示。
三、解析
解析是渲染引擎中非常重要的一个环节。
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,而是将这项工作交由主解析器处理。预解析器只会解析外部资源(例如外部脚本、样式表和图片)的引用。
未完待续...