[译] JavaScript 工做原理:渲染引擎及其性能优化

好久没有翻译文章了,最近看到一篇不错的文章,恰好安排上。javascript

原文地址:How JavaScript works: the rendering engine and tips to optimize its performancecss

JavaScript 工做原理:渲染引擎及其性能优化

这是探讨 JavaScript 系列文章的第 11 篇,专门探讨 JavaScript 及其构建组件。识别和描述核心元素的过程当中,咱们还分享了一些在构建 SessionStack 时使用的经验法则。SessionStack 是一个须要强大且高性能的 JavaScript 应用程序,以帮助用户实时发现并重现他们的 Web 应用程序缺陷。html

若是你想阅读系列的其余文章,能够看下面的连接:html5

到目前为止,在咱们以前的 "JavaScript的工做原理" 系列博客文章中,咱们一直专一于JavaScript做为一种语言,其功能,如何在浏览器中执行,如何对其进行优化等。java

可是,在构建 Web 应用程序时,不只仅只是会编写独立运行的 JavaScript 代码。您编写的 JavaScript 代码须要和环境进行交互。了解此环境,和其工做方式以及它的组成将使您可以构建更好的 web 应用程序,并能为后续应用程序发布后可能出现的潜在问题作好充分的准备。 node

那么,咱们来看看浏览器主要由哪些部分组成:git

  • 用户界面:包括地址栏、后退和前进按钮、书签菜单等等。大致上,这是除了浏览网页的窗口之外浏览器所展现的部分。
  • 浏览器引擎:它处理用户界面和渲染引擎之间的交互。
  • 渲染引擎:它负责显示网页。渲染引擎解析 HTML 和 CSS,并在屏幕上显示解析后的内容。
  • 网络层:这些是不一样的平台使用不一样的方式实现的网络调用,如 XHR 请求,位于独立于平台的接口后面。 在本系列以前的文章中,咱们更详细地讨论了网络层。
  • UI 后台:它用于绘制核心小部件,如复选框和窗口。该后台暴露了一个非平台特定的通用接口。它使用操做系统底层的 UI 方法。
  • JavaScript 引擎:在本系列以前的文章中,咱们已进行了详细的介绍。大致就是,这是 JavaScript 执行的地方。
  • 数据持久化:你的应用程序可能须要在本地存储全部数据。支持的存储机制类型包括 localStorageindexDBWebSQLFileSystem

本文中,咱们将重点介绍渲染引擎,由于它处理 HTML 和 CSS 的解析和可视化,并且大多数 JavaScript 应用程序会一直与之交互。github

渲染引擎概述

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

渲染引擎能够显示 HTML 和 XML 文件和图像。若是你使用其余的插件,该引擎还能显示不一样类型的文档,如 PDF。编程

渲染引擎

与 JavaScript 引擎相似,不一样的浏览器也使用不一样的渲染引擎。下面是一些流行的渲染引擎:

  • Gecko — Firefox
  • WebKit — Safari
  • Blink — Chrome, Opera (从版本15起)

渲染过程

渲染引擎从网络层接收所请求文档的内容。

构造DOM树

渲染引擎的第一步是解析 HTML 文档,并将解析的元素转换为DOM 树 中的实际 DOM 节点。

假设你输入了如下文本:

<html>
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="theme.css">
  </head>
  <body>
    <p> Hello, <span> friend! </span> </p>
    <div> 
      <img src="smiley.gif" alt="Smiley face" height="42" width="42">
    </div>
  </body>
</html>
复制代码

该 HTML 文档的 DOM 树看起来像下面这样:

基本上,每一个元素都表示为其直接所包含的全部元素的父节点,而且这是递归进行的。

构造 CSSOM 树

CSSOM 指的是 CSS 对象模型。当浏览器构建页面的 DOM 时,在 head 部分,遇到引用外部层叠样式表 theme.csslink 标签时,预计可能须要这个资源来渲染页面,它就当即为此发送了一个请求。 假设 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 标签所包含的全部文本,字体大小都是 16px 而且颜色为红色。这些样式是从 body 元素继承的。 若是 span 元素是 p 标签的子元素,则会由于设置了更具体的样式,而不会显示其内容。

另外,请注意,上面的树不是完整的 CSSOM 树,它只显示了样式表中咱们决定重写的样式。每一个浏览器都提供了一组默认的样式,也称为 用户代理样式 — 也就是咱们没有明确提供任何样式时会看到的样式。 咱们的样式只是覆盖了这些默认样式。

构建渲染树

HTML 中的视觉指令与 CSSOM 树中的样式数据相结合,用于建立渲染树。

你可能会问什么是渲染树?这是一个可视化元素树,按照它们在屏幕上显示的顺序构建。它是 HTML 和相应的 CSS 的可视化表示。此树的目的是使内容可以按正确的顺序进行绘制。

渲染树中的每一个节点称为渲染器或WebKit中的渲染对象。

这里是上面的 DOM 和 CSSOM 树的渲染器树的样子:

要构造渲染树,浏览器大体执行如下操做:

  • 从 DOM 树的根节点开始,它遍历每一个可见节点。某些节点是不可见的(例如,script 标签,meta 标签等等),而且因为它们不进行渲染输出而被忽略。还有些经过 CSS 隐藏的节点,也会从渲染树中省略。例如,上例中的 span 标签 — 在渲染树中不存在,由于咱们对其设置了 display:none 属性。
  • 对每一个可见节点,浏览器会找到合适的匹配 CSSOM 规则并应用它们。
  • 它发出带有内容及其计算样式的可见节点。

你能够在这里查看 RenderObject 的源代码(在webkit中):github.com/WebKit/webk…

让咱们来看看这个类的核心内容:

class RenderObject : public CachedImageClient {
  // Repaint the entire object.  Called when, e.g., the color of a border changes, or when a border
  // style changes.

  Node* node() const { ... }

  RenderStyle* style;  // the computed style
  const RenderStyle& style() const;

  ...
}
复制代码

每一个渲染器表明一个一般与某节点的 CSS 盒子相对应的矩形区域。它包含了一些几何信息,如宽,高和位置。

渲染树的布局

当渲染器被建立并添加到树中时,它是没有位置和大小的。而计算这些值就称为布局。

HTML 使用基于流的布局模型,这意味着它大多数状况下能够一次性计算出几何结构。坐标系是相对于根渲染器的,使用顶部和左侧坐标。

布局是一个递归的过程 — 它从对应于 HTML 文档 <html> 元素的根渲染器开始。布局经过部分或整个渲染器的层级继续递归,为每一个依赖它的渲染器计算几何信息。

根渲染器的位置为0,0,其尺寸与浏览器窗口(即视区)的可见部分大小相同。

布局过程的开始意味着给每一个节点赋予应该在屏幕中显示的精确坐标。

绘制渲染树

在此阶段,将遍历渲染器树,并调用渲染器的 paint() 方法在屏幕上显示内容。

绘制能够是全局的或递增的(相似于布局):

  • 全局 - 从新绘制整个树。
  • 递增 - 只有部分渲染器以不会影响整个树的方式改变。渲染器使屏幕上的矩形无效。这将致使操做系统将其视为须要重绘的区域并生成一个 paint 事件。操做系统经过一种将多个区域合并为一个区域的智能方式来实现这一点。

一般来讲,了解绘制是一个渐进的过程是很是重要的。为了更好的用户体验,渲染引擎会尽量快地将内容显示在屏幕上。它不会等到全部的 HTML 都被解析后才开始构建和绘制渲染树。部份内容将被解析并显示,同时该过程会继续处理来自网络的其他内容项。

处理脚本和样式表的顺序

当解析器到达 <script> 标签时,将当即解析并执行脚本。在脚本执行以前,将中止对文档的解析,这意味着该过程是同步的。

若是脚本是外部的,那么首先必须从网络中获取它(也是同步的)。在获取完成以前,全部解析都将中止。

HTML5 添加了一个选项,将脚本标记为异步脚本,以便由其余线程解析和执行。

优化渲染性能

若是你想优化你的应用程序,你须要关注五个主要方面。如下是你能够控制的方面:

  • 1.JavaScript — 在以前的文章中,咱们讨论了编写优化代码的主题,优化的代码不会阻塞 UI,内存效率高等等。当涉及到渲染时,咱们须要考虑 JavaScript 代码与页面上的 DOM 元素交互的方式。JavaScript 能够在 UI 中建立大量的更改,特别是在单页应用中。
  • 2.样式计算 - 这是根据匹配的选择器肯定哪一个 CSS 规则应用于哪一个元素的过程。一旦规则被定义,将应用这些规则并计算每一个元素的最终样式。
  • 3.布局 - 一旦浏览器知道哪些规则应用于某个元素,它就能够开始计算元素占用的空间以及它在浏览器屏幕上的位置。web 的布局模型定义了一个元素能够影响其余元素。例如, 的宽度能够影响其子级的宽度等。这意味着布局过程是计算密集型的而且绘制是在多层上完成的。
  • 4.绘制 - 这是填充实际像素的步骤。这个过程包括绘制文本、颜色、图像、边框、阴影等 — 每一个元素的每一个可视部分。
  • 5.合成 - 因为页面部分被绘制到潜在的多个层中,因此它们须要以正确的顺序绘制到屏幕上,以便页面正确渲染。这一点很是重要,特别是对于重叠的元素。

优化 JavaScript

JavaScript 常常触发浏览器中的视觉变化。尤为是构建单页应用时。

下面是一些关于你能够优化 JavaScript 的哪些部分来改进渲染的建议:

  • 避免使用 setTimeoutsetInterval 进行视觉更新。它们将在帧的某个不肯定时刻调用 “callback”,可能在帧结束时执行。咱们要作的是在帧开始时触发视觉变化而不要错过它。
  • 将长时间运行的 JavaScript 计算交给 Web Workers 处理,就像咱们以前讨论的同样。
  • 使用微任务进行多帧的 DOM 更新。这是为了防止任务须要访问 DOM,而 Web Workers 没法访问 DOM。这基本上意味着你能够将一个大任务分解成更小的任务,并根据任务的性质在 requestAnimationFramesetTimeoutsetInterval 中运行它们。

优化 CSS

经过添加和删除元素,修改属性等更改 DOM 将使浏览器从新计算元素样式,在许多状况下,还会从新计算整个页面或至少部分页面的布局。

要优化渲染,请考虑如下内容:

  • 减小选择器的复杂性。与构建样式自己的其余工做相比,选择器复杂性能占用计算元素样式所需的50%以上的时间。
  • 减小必须对其进行样式计算的元素的数量。大致上就是直接对一些元素进行样式更改,而不是使整个页面失效。

优化布局

布局的从新计算对于浏览器来讲可能很是繁重。能够考虑如下方面的优化:

  • 尽量减小布局的数量。更改样式时,浏览器将检查是否有任何更改须要从新计算布局。对 width,height,left,top 等属性以及一般与几何图形相关的属性的更改,都须要布局。因此,尽可能避免改变这些属性。
  • 尽可能在旧的布局模型上使用 flexbox。它渲染得更快,能够为你的应用程序创造巨大的性能优点。
  • 避免进行强制的同步布局。须要记住的是,当 JavaScript 运行时,来自前一帧的全部旧的布局值都是已知且能获取的。因此,若是你获取 box.offthesight 是没有问题的。可是,若是在获取前更改了盒子的样式(例如,给元素动态添加一些 CSS 类),浏览器必须先去更改样式,而后再进行布局。这可能很是耗费时间和资源,所以要尽量避免。

优化绘制

绘制一般是全部任务中运行时间最长的,所以尽量避免绘制是很是重要的。咱们能够这样作:

  • 更改除了 transforms 或 opacity 之外的任何属性都会触发绘制。请谨慎使用。
  • 只要触发布局就会触发绘制,由于更改几何结构会致使元素的视觉的更改。
  • 经过图层优化和动画编排减小绘制区域。

渲染是 SessionStack 正常运转的一个重要方面。SessionStack 必须将用户在浏览 Web 应用程序时遇到问题时发生的全部事情从新建立成一个视频。为此,SessionStack 仅利用咱们的库收集的数据:用户事件、DOM更改、网络请求、异常、调试消息等。咱们的播放器通过高度优化,可以正确地渲染和使用全部收集的数据,以便不管从视觉上仍是技术上为用户的浏览器和浏览器中发生的一切提供一个完美像素模拟。

若是你想 试一试 SessionStack,有一个免费的计划。

参考资源

相关文章
相关标签/搜索