JavaScript工做原理(十):渲染引擎和优化性能的技巧

到目前为止,在咱们以前的“JavaScript工做原理”系列文章中,咱们一直关注JavaScript做为一种语言,其功能,它如何在浏览器中执行,如何优化等等。css

可是,当您构建Web应用程序时,您不仅是编写独立运行的独立JavaScript代码。您编写的JavaScript与环境进行交互。了解这种环境,它是如何工做的以及它的组成是什么,将使您可以构建更好的应用程序,并对应用程序发布后可能出现的潜在问题作好充分准备。
图片描述html

那么,让咱们看看浏览器的主要组件是什么:node

  • 用户界面:这包括地址栏,后退和前进按钮,书签菜单等。实质上,这是浏览器显示的每一个部分,除了您看到网页自己的窗口。
  • 浏览器引擎:它处理用户界面和渲染引擎之间的交互
  • 渲染引擎:它负责显示网页。渲染引擎解析HTML和CSS,并在屏幕上显示解析的内容。
  • 网络:这些是网络调用,例如XHR请求,经过对不一样平台使用不一样的实现来实现,这些平台位于独立于平台的接口以后。在本系列的前一篇文章中,咱们更详细地讨论了网络层。
  • UI后端:用于绘制核心小部件,如复选框和窗口。这个后端公开了一个不是平台特定的通用接口。它使用下面的操做系统UI方法。
  • JavaScript引擎:咱们在该系列的前一篇文章中详细介绍了这一点。基本上,这是JavaScript执行的地方。
  • 数据持久性:您的应用可能须要在本地存储全部数据。支持的存储机制类型包括localStorage,indexDB,WebSQL和FileSystem。

在这篇文章中,咱们将关注渲染引擎,由于它处理HTML和CSS的解析和可视化,这是大多数JavaScript应用程序不断与之交互的东西。git

渲染引擎的概述

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

渲染引擎能够显示HTML和XML文档和图像。若是您使用额外的插件,引擎还能够显示不一样类型的文档,如PDF。web

渲染引擎

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

  • Gecko - 火狐
  • 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时,它在引用外部theme.css CSS样式表的head部分遇到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元素继承而来的。若是span元素是p元素的子元素,则因为正在应用更具体的样式,所以不会显示其内容。

另外请注意,上面的树不是完整的CSSOM树,只显示了咱们决定在样式表中重写的样式。每一个浏览器都提供了一组默认的样式,也称为“user agent styles” - 这是咱们在没有明确提供任何样式时看到的。咱们的样式简单地覆盖这些默认值。

构建渲染树

HTML中的可视指令与CSSOM树中的样式数据结合在一块儿用于建立渲染树。

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

Webkit中,渲染树中的每一个节点都被称为的渲染器或渲染对象。

这就是上述DOM和CSSOM树的渲染器树的外观:
图片描述

为了构建渲染树,浏览器大体以下:

  • 从DOM树的根开始,它遍历每一个可见节点。某些节点不可见(例如,脚本标记,元标记等),而且因为它们未反映在呈现的输出中而被忽略。一些节点经过CSS隐藏,而且也从渲染树中省略。例如,span节点 - 在上面的例子中,它并不存在于渲染树中,由于咱们有一个明确的规则来设置display:none属性。
  • 对于每一个可见节点,浏览器找到适当的CSSOM规则并应用它们。
  • 它发出带有内容及其计算样式的可见节点

你能够在这里看看RenderObject的源代码(在WebKit中):https://github.com/WebKit/web...

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

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()方法以在屏幕上显示内容。

绘画能够是全局或增量式(与布局相似):

  • 全局 - 整个树被从新绘制。
  • 增量 - 只有一些渲染器以不影响整个树的方式进行更改。渲染器使其矩形在屏幕上无效。这会致使操做系统将其视为须要重绘和生成绘画事件的区域。操做系统经过将几个区域合并为一个智能方式来完成。

通常来讲,了解绘画是一个渐进的过程是很重要的。为了更好的用户体验,渲染引擎会尝试尽快在屏幕上显示内容。它不会等到全部的HTML被解析,才开始构建和布置渲染树。内容的部份内容将被解析并显示,而该过程继续保持来自网络的其他内容项目。

处理脚本和样式表的顺序

当解析器到达<script>标记时,脚本将被当即解析并执行。文档解析暂停,直到脚本执行完毕。这意味着该过程是同步的。

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

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

优化渲染性能

若是您想优化您的应用,那么您须要关注五个主要方面。这些是您能够控制的区域:

  • JavaScript - 在以前的文章中,咱们介绍了编写优化代码的主题,这些代码不会阻止UI,内存效率高等等。当涉及渲染时,咱们须要考虑JavaScript代码与DOM元素之间的交互方式这一页。JavaScript能够在UI中建立大量更改,尤为是在SPA中。
  • 样式计算 - 这是肯定哪一个CSS规则适用于基于匹配选择器的元素的过程。一旦定义了规则,就会应用这些规则,并计算每一个元素的最终样式。
  • 布局 - 一旦浏览器知道哪些规则适用于元素,就能够开始计算后者占用的空间以及它在浏览器屏幕上的位置。 Web的布局模型定义了一个元素可能会影响其余元素。例如,<body>的宽度会影响其子元素的宽度等等。这一切都意味着布局过程是计算密集型的。该绘图是在多个层次完成的。
  • Paint - 这是实际像素被填充的位置。该过程包括绘制文本,颜色,图像,边框,阴影等 - 每一个元素的每一个视觉部分。
  • 合成 - 因为页面部分被划分为多个图层,所以须要按照正确的顺序将其绘制到屏幕上,以便页面呈现正确。这很是重要,特别是对于重叠元素。

优化您的JavaScript

JavaScript常常触发浏览器中的视觉变化。当创建一个SPA时更是如此。

如下是关于JavaScript能够优化哪些部分以改善渲染的一些提示:

  • 为视觉更新避免setTimeout或setInterval。这些将在框架中的某个点调用回调,最后可能会发生。咱们想要作的就是在画面开始时触发视觉变化,不要错过它。
  • 如前所述,将长时间运行的JavaScript计算移至Web Workers。
  • 使用微任务在多个框架中引入DOM更改。这是为了防止任务须要访问DOM,Web Worker不能访问这些任务。这基本上意味着你将一个大任务分解成更小的任务,并根据任务的性质在requestAnimationFrame,setTimeout,setInterval中运行它们。

优化你的CSS

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

要优化渲染,请考虑如下几点:

  • 减小选择器的复杂性。选择器的复杂度可能比计算元素样式所需的时间多50%,而构建样式自己的其他工做则须要花费超过50%的时间。
  • 减小style计算必须发生的元素数量。实质上,直接对几个元素进行样式更改,而不是使整个页面失效。

优化布局

浏览器的布局从新计算可能很是繁重。考虑如下优化:

  • 尽量减小布局的数量。当您更改样式时,浏览器会检查是否有任何更改要求从新计算布局。对属性(如宽度,高度,左侧,顶部以及一般与几何相关的属性)的更改须要布局。因此,尽可能避免改变它们。
  • 尽量在较早的布局模型上使用Flexbox。它运行速度更快,可为您的应用程序创造巨大的性能优点。
  • 避免强制同步布局。须要记住的是,在JavaScript运行时,前一帧中的全部旧布局值都是已知的,可供您查询。若是你访问box.offsetHeight它不会是一个问题。可是,若是您在访问该框以前更改了框的样式(例如,经过向元素动态添加一些CSS类),浏览器必须先应用样式更改并运行布局。这可能很是耗时且耗费资源,所以请尽量避免。

优化paint

这一般是全部任务中运行时间最长的,所以尽量避免这种状况很是重要。如下是咱们能够作的事情:

  • 更改除变换或不透明度之外的任何属性都会触发绘画。谨慎使用它。
  • 若是触发layout,则还会触发paint,由于更改几何图形会致使元素的视觉更改。
  • 经过图层提高和动画编排来减小绘画区域。