资讯专栏INFORMATION COLUMN

JavaScript 工作原理之十一-渲染引擎及性能优化小技巧

RyanQ / 1751人阅读

摘要:在中渲染树中的每个节点即是一个渲染器或者渲染器对象。计算的样式每个渲染器对象代表一个矩形区域通常是和一个节点的盒模型相对应。坐标系统是相对于根渲染器的。根渲染器的定位为和大小即为浏览器窗口的可视化部分比如。渲染器作废其在屏幕上的矩形区域。

原文请查阅这里,略有删减,本文采用知识共享署名 4.0 国际许可协议共享,BY Troland。

本系列持续更新中,Github 地址请查阅这里。

这是 JavaScript 工作原理的第十一章。

迄今为止,之前的 JavaScript 工作原理系列文章集中于关注 JavaScript 语言本身的功能,在浏览器中的执行情况,如何优化等等。

然而,当在构建网络应用的时候,不仅仅只是编写自己运行的 JavaScript 代码。所编写的 JavaScript 代码与运行环境息息相关。理解 JavaScript 运行环境,它的运行原理以及其组成会让你构建出更好的应用并且一旦让应用程序运行于各种环境下的时候,让你更加胸有成竹地应对潜在的问题。

那么,让我们一探浏览器主要组件吧:

用户界面: 包括地址栏,后退和前进按钮,书签菜单等等。本质上,这里包含了除显示用户所看到的网页本身的窗口以外的浏览器的每个部分。

浏览器引擎: 处理用户界面和渲染引擎的交互

渲染引擎: 负责显示网页。渲染引擎解析 HTML 和 CSS 并在屏幕上显示解析的内容。

网络: 使用各个平台的不同实现所发起的诸如 XHR 请求的网络调用,这些网络调用是基于跨平台的接口实现的。

UI 后端: 负责绘制诸如复选框和窗口的核心部件。它暴露出一个平台无关的泛型接口。它底层使用操作系统 UI 方法。

JavaScript 引擎: 我们在之前的系列文章中有详细介绍过。基本上,这是 JavaScript 代码执行的地方。

数据存储: 网络应用可能需要本地存储所有数据。支持的存储机制类型包括 localStorage, indexDB, WebSQL 以及 FileSystem。

本文将专注介绍渲染引擎,因为它是用来处理 HTML 和 CSS 的解析和可视化的,而这些是大多数的 JavaScript 应用需要持续进行交互的方面。

渲染引擎概述

渲染引擎的主要职责即在浏览器屏幕上显示请求的页面。

渲染引擎可以显示 HTML,XML 文档以及图片。如果使用额外的插件,就可以显示诸如 PDF 的不同类型的文档。

渲染引擎

与 JavaScript 引擎类似,不同浏览器也使用不同的渲染引擎。以下为比较流行的引擎:

Gecko-Firefox

WebKit-Safari

Blink-Chrome, Opera(从版本 15 开始)

渲染过程

渲染引擎从网络层获取到请求的文档内容。

构建 DOM 树

渲染引擎的第一步即解析 HTML 文档和转化解析的元素为 DOM 树 上的实际 DOM 节点。

假设有如下的文本输入框:


  
    
    
  
  
    

Hello, friend!

Smiley face

HTML 的 DOM 树类似这样:

基本上,每个元素是直接包含于其内的元素的父节点。然后依次类推。

构建 CSSOM 树

CSSOM 即 CSS Object Model。当浏览器构建页面的 DOM 树的时候,它在 head 标签部分遇到一个引用外部 theme.css 样式表的 link 标签。表示它可能需要样式表来渲染页面,于是便马上分派一个请求来获取样式表。假设以下为 theme.css 文件内容:

body { 
  font-size: 16px;
}

p { 
  font-weight: bold; 
}

span { 
  color: red; 
}

p span { 
  display: none; 
}

img { 
  float: right; 
}

与 HTML 一样,渲染引擎需要把 CSS 转化为浏览器可以操作的东西-即 CSSOM。以下为 CSSOM 的大概模样:

想知道为什么 CSSOM 是树状结构的吗?当为页面上的任意对象计算其最终的样式集的时候,浏览器先把最为通用的样式规则应用于该节点(比如,它是 body 的子节点,会先应用 body 的所有样式)然后通过应用更为具体的样式规则来递归重定义计算的样式。

让我们看下具体的例子吧。body 中的 span 标签中的任何文字样式为字体大小 16 像素且字体颜色为红色。这些样式继承自 body 元素。p 元素的子元素 span 由于应用了更为具体的样式从而不会显示其内容(display:none)。

还有,请注意以上 CSSOM 树并不完整而且只显示了样式表中指定的重写样式。每个浏览器提供了一份默认的样式集即 『用户代理样式』- 这即当没有提供任何样式的时候的默认显示样式。我们的样式只是简单地重写了这些默认样式。

构建渲染树

HTML 中的可视化指令和 CSSOM 树的样式数据结合起来创建渲染树。

你可能为问渲染树是什么?它是按顺序构建可视化元素并显示在屏幕上的树。它是带有相应的样式的 HTML 的视觉表现。该树旨在按正确的顺序绘制内容。

在 Webkit 中渲染树中的每个节点即是一个渲染器或者渲染器对象。

以下为以上的 DOM 和 CSSOM 树合成的渲染器树的大概模样:

为了创建渲染树,浏览器大概做了几下几件事:

从 DOM 树的根节点开始,遍历每个可见节点。一些节点是不可见的(比如,script 标签,meta 标签等等),然后会被忽略,因为它们并不会在渲染的输出中显示。一些节点通过样式隐藏然后也会被忽略。比如以上例子中的 span 节点,因为为其显式设置了 display: none 的样式。

浏览器为每个可见节点应用相对应的 CSSOM 规则并应用这些样式规则。

释放出包含内容及其经过计算的样式的可见节点。

可以浏览下 RenderObject 的源码(Webkit 中):https://github.com/WebKit/web...

看一下这个类的一些核心构件吧:

class RenderObject : public CachedImageClient {
  // 重绘整个对象。当边框颜色改变或者边框样式更改的时候调用。
  
  Node* node() const { ... }
  
  RenderStyle* style;  // 计算的样式
  const RenderStyle& style() const;
  
  ...
}

每个渲染器对象代表一个矩形区域通常是和一个节点的 CSS 盒模型相对应。它包括诸如宽度,高度以及定位的几何信息。

渲染树布局

当创建了渲染器并且添加到渲染树的时候,它并没有定位和大小的信息。计算这些值即称为布局。

HTML 使用了流式布局模型,意即大多数情况下可以一次性计算出渲染器的几何信息。坐标系统是相对于根渲染器的。这里使用 Top 和 left 坐标。

布局是一个递归的过程-它从根渲染器开始进行渲染,根渲染器即 HTML 文档的 html 元素。布局继续通过一部分或者整个渲染器层级结构递归进行,为每个需要计算几何信息的渲染器计算其信息。

根渲染器的定位为 0,0 和大小即为浏览器窗口的可视化部分(比如 viewport)。

进行布局的过程即计算出每个节点在屏幕上显示的准确位置。

绘制渲染树

该阶段,遍历渲染器树然后调用渲染器的 paint() 方法来在屏幕上显示其内容。

绘制可以是全局或增量式的(类似于布局):

全局-重绘整个树。

增量-以某种方式只更改部分渲染器而不会影响到整颗树。渲染器作废其在屏幕上的矩形区域。这会导致操作系统把它看成是一个需要重绘的区域并生成一个 paint 事件。操作系统会智能地把几个区域合并成一个以提升渲染性能。

总之,理解绘制是个渐进式的过程是很重要的。为了更好的交互体验,渲染引擎会试图尽快在屏幕上显示内容。它不会等待所有的 HTML 结构解析完成才开始构建和布局渲染树。会优先解析和显示部分内容,与此同时持续处理从网络接收的剩下的内容项。

脚本和样式的处理顺序

当解析器遇到

阅读需要支付1元查看
<