这是探索 JavaScript 及其构建组件专题系列的第 11 篇。在识别和描述核心元素的过程当中,咱们分享了在构建 SessionStack 时使用的一些经验法则。SessionStack 是一个须要鲁棒且高性能的 JavaScript 应用程序,它帮助用户实时查看和重现它们 Web 应用程序的缺陷。javascript
当构建 Web 应用程序时,你不仅是编写独立运行的 JavaScript 代码片断。你编写的 JavaScript 须要与环境进行交互。理解环境是如何工做的以及它是由什么组成的,你就可以构建更好的应用程序,而且能更好地处理应用程序发布后才会显现的潜在问题。css
那么,让咱们看看浏览器的主要组件有哪些:html
在这篇文章中,咱们将关注渲染引擎,由于它负责处理 HTML 和 CSS 的解析和可视化,这是大多数 JavaScript 应用程序不断与之交互的地方。前端
渲染引擎的主要职责是在浏览器屏幕上显示所请求的页面。html5
渲染引擎能够显示 HTML / XML 文档和图像。若是你使用其余插件,它还能够显示不一样类型的文档,例如 PDF。java
与 JavaScript 引擎相似,不一样的浏览器也使用不一样的渲染引擎。常见的有这些:node
渲染引擎从网络层接收所请求文档的内容。android
渲染引擎的第一步是解析 HTML 文档并将解析出的元素转换为 DOM 树 中实际的 DOM 节点。ios
假设你有如下文字输入:git
<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 指 CSS 对象模型。当浏览器构建页面的 DOM 时,它在 head
中遇到了一个引用外部 theme.css
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
元素继承而来的。 若是一个 span
元素是一个 p
元素的子元素,那么它的内容就不会被显示,由于它被应用了更具体的样式(display: none
)。
另外请注意,上面的树不是完整的 CSSOM 树,只显示了咱们决定在样式表中重写的样式。每一个浏览器都提供了一组默认的样式,也称为 「用户代理样式」——这是咱们在未明确指定任何样式时看到的样式。咱们的样式会覆盖这些默认值。
HTML 中的视图指令与 CSSOM 树中的样式数据结合在一块儿用来建立渲染树。
你可能会问什么是渲染树。渲染树是一颗由可视化元素以它们在屏幕上显示的顺序而构成的树型结构。它是 HTML 和相应的 CSS 的可视化表示。此树的目的是为了以正确的顺序绘制内容。
渲染树中的节点被称为 Webkit 中的渲染器或渲染对象。
这就是上述 DOM 和 CSSOM 树的渲染器树的样子:
为了构建渲染树,浏览器大体作了以下工做:
display: none
属性。你能够在这里查看 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 使用基于流的布局模型,这意味着大部分时间内它能够在一次遍历中(single pass)计算出布局。坐标系是相对于根渲染器的,使用左上原点坐标。
布局是一个递归过程 —— 它从根渲染器开始,对应于 HTML 文档的 <html>
元素,经过部分或整个渲染器的层次结构递归地为每一个须要布局的渲染器计算布局信息。
根渲染器的位置是 0,0
,而且其尺寸为浏览器窗口(也称为视口)的可见部分的尺寸。
开始布局过程意味着给出每一个节点它应该出如今屏幕上的确切坐标。
在这个阶段,浏览器遍历渲染器树,调用渲染器的 paint()
方法在屏幕上显示内容。
绘图能够是全局的或增量式的(与布局相似):
paint
事件的区域。操做系统经过将几个区域合并为一个区域的智能方式来完成绘图。通常来讲,了解绘图是一个渐进的过程是很重要的。为了更好的用户体验,渲染引擎会尝试尽快在屏幕上显示内容。它不会等到全部的 HTML 被分析完毕才开始构建和布置渲染树。一小部份内容先被解析并显示,同时一边从网络获取剩下的内容一边渐进地渲染。
当解析器到达 <script>
标签时,脚本将被当即解析并执行。文档解析将会被暂停,直到脚本执行完毕。这意味着该过程是同步的。
若是脚本是外部的,那么它首先必须从网络获取(也是同步的)。全部解析都会中止,直到网络请求完成。
HTML5 添加了一个选项,能够将脚本标记为异步,此时脚本被其余线程解析和执行。
若是你想优化你的应用,那么你须要关注五个主要方面。这些是您能够控制的地方:
<body>
的宽度会影响子元素的宽度等等。这一切都意味着布局过程是计算密集型的。该绘图是在多个图层完成的。JavaScript 常常触发浏览器中的视觉变化,构建 SPA 时更是如此。
如下是关于能够优化 JavaScript 哪些部分来改善渲染性能的一些小提示:
setTimeout
或 setInterval
进行视图更新。这些将在帧中某个不肯定的时间点上调用 callback
,可能在最后。咱们想要作的是在帧开始时触发视觉变化而不是错过它。requestAnimationFrame
、setTimeout
或 setInterval
中运行它们。经过添加和删除元素、更改属性等来修改 DOM 会致使浏览器从新计算元素样式,而且在不少状况下还会从新布局整个页面或至少其中的一部分。
要优化渲染性能,请考虑如下方法:
布局的从新计算会对浏览器形成很大压力。请考虑下面的优化:
flexbox
而不是老的布局模型。它运行速度更快,可为你的应用程序创造巨大的性能优点。box.offsetHeight
是没问题的。 可是,若是你在查询元素以前更改了元素的样式(例如,动态向元素添加一些 CSS 类),浏览器必须先应用样式更改并执行布局过程。这可能很是耗时且耗费资源,所以请尽量避免。优化绘图
这一般是全部任务中运行时间最长的,所以尽量避免这种状况很是重要。 如下是咱们能够作的事情:
渲染是 SessionStack 运行的重点之一。当用户浏览你的 web 应用遇到问题时,SessionStack 必须将这些遇到的问题重建成一个视频。为了作到这点,SessionStack 仅利用咱们的库收集到数据:用户事件、DOM 更改、网络请求、异常和调试消息等。咱们的播放器通过高度优化,可以按顺序正确呈现和使用全部收集到的数据,从视觉和技术两方面为你提供用户在浏览器中发生的一切的像素级完美模拟。
若是你想试试看,这里能够免费尝试 SessionStack。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。