天天看点

DOM 的层级深度影响性能

作者:闪念基因

当使用 Lighthouse 来测量网站性能时,你可能会遇到 “避免 DOM 过多” 警告。它看起来像这样:

DOM 的层级深度影响性能

Lighthouse 警告我们避免 DOM 过多,是因为 DOM 会增加内存使用量,并且可能会导致昂贵的样式计算。结合你网站上发生的其他事情,这可能会对用户体验产生影响,尤其是对于使用低端设备的用户。

前几天,当我阅读网站性能报告时,这个警告引起了我的注意。但引起我重新审视的并不是 DOM 元素的总数,而是报告中的另一项指标 —— 最大 DOM 深度。当我在 Lighthouse 报告中看到这个指标时,在脑海中涌现出了一个问题:

DOM 深度如何影响渲染性能呢?

当我们使用像 DOM 这样的树状数据结构时,其深度与诸如查找等操作的执行速度有很大关系。看看这两棵 DOM 树:

DOM 的层级深度影响性能

在上图中两棵树的元素总数是相同。但是一棵层级较浅,深度(或高度)为 2;另一棵层级较深, 深度为 6。这种差异很重要,因为树越深,访问其元素可能需要的操作就越多。

例如,想象一下,我们想从树的根部访问 <img> 元素。在第一棵层级浅的树上,我们只需两个操作即可实现这一点——找到 <body> 的子元素数组,然后访问索引为 4 的子元素:

body.children[4];
           

在第二棵层级较深的树上,我们需要进行六次跳转才能到达相同的元素:

body.children[0].children[0].children[0].children[0].children[0];
           

树的深度在处理某些类型的树时特别重要,比如二叉搜索树(BST)。这就是为什么有这么多不同的数据结构实现自平衡的 BST,以便我们可以在树增长时保持树的高度最小化,并尽可能地使查找等操作快速进行。

回到 DOM 的问题上,理论上,树越深,它的速度会越慢。但实际上,这对性能有多大影响呢?

让我们进行一个小实验来找出答案。

简单测试

为了方便测试,我准备了两个 HTML 页面,这两个页面都包括一行文本和 100 个空的 div,唯一的区别是一个页面是所有的 div 直接放在文档的 body 中,而另一个页面中的 div 是嵌套的。

我准备了几个只包含三行文本和 100 个空 div 的 HTML 页面。这两个页面之间唯一的区别是一个页面中所有的 div 直接放在文档的 body 中,而另一个页面中的 div 是嵌套的。

因此,层级浅的页面看起来是这样的:

<html>
  <body>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <!-- 95 divs later... -->
    <div>This is the last of 100 divs.</div>
  </body>
</html>
           

另一个层级比较深的页面是这样:

<html>
  <body>
    <div>
      <div>
        <div>
          <div>
            <!-- 95 divs later... -->
            <div>This is the last of 100 divs.</div>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>
           

然后测试结果是,第一个页面的加载时间(解析 + 渲染 + 绘制)为 51 毫秒。而在第二个页面上,页面加载时间竟然是… 53 毫秒。看起来两者差别不大。接着我测试了 200 个、 300 个、400个,最后到了 500 个 div,性能上就有了明显的差异。

DOM 的层级深度影响性能

层级浅的示例页面渲染 500 个 div 耗时 56 毫秒,这个页面几乎没有受到元素数量增加的影响;但是嵌套层级深的页面花费了 102 毫秒,几乎是前者的两倍。所以我继续进行测试,测试了多达 5000 个 div。以下是结果:

DOM 的层级深度影响性能

值得注意的是,两个页面的大小是相同的,它们都包含相同数量的元素,在浏览器渲染时它们看起来完全相同。唯一的区别是 DOM 树的深度,正如我们在上面的图表中所看到的,这确实会影响渲染性能。虽然,你可能会认为一个 5,000 的 DOM 深度是一个荒谬的想法,因为真正的网站永远不会那么糟糕... 你完全是对的。

公平地说,5,000 个元素并不是那么罕见,在一个真实的网站上,所有这些元素都不会像在浅树示例中那样处于相同的深度。即使在相对较低的深度 32 的情况下,解析、渲染和绘制这么多元素也需要几百毫秒的时间 —— 而且这还是在 CSS 和 JS 加入之前,CSS 和 JS 会让性能进一步变差。

##加入 CSS 样式##

说到 CSS,对于层级较深 DOM 树,一个潜在性能问题是昂贵的样式重新计算。我们在 5,000 个嵌套 div 的基础上,为每个 div 添加一些文本,然后添加一个 CSS 规则来测试样式重新计算的影响:

div {
  padding-top: 10px;
}
           

由于这个规则几乎影响到页面上的每一个元素,浏览器必须花费很长时间来计算更新每一个 div 的位置。这个昂贵的样式重新计算任务可能会阻塞主线程,使我们的网站变得响应速度下降。

DOM 的层级深度影响性能

在这个实验中,我们只测量了页面加载性能, 在真实环境中,用户在初始加载之后也会与页面进行交互。这就是密切关注 DOM 大小和深度的原因。不是因为它们会使您的网站加载变慢,而是因为它们为所有其他运行时操作(比如用户通过 JavaScript 更新 DOM)建立了基线。

那么我们能做些什么呢?

首先要定期检查 DOM 大小和深度。但由于我们通常只与 UI 组件或模板部分打交道,这导致我们平时只接触到一点点 HTML,所以很容易忽略了这些日益增多的元素。

我们可以使用类似 Lighthouse 或 PageSpeed Insights 这样的工具来测量网站中 DOM 大小和深度。如果想快速检查页面上有多少元素,您可以在浏览器的控制台上运行以下代码:

document.querySelectorAll("*").length;
           

另一个非常有用的方法是,减少 CSS 选择器的范围和复杂性。这样可以让浏览器更轻松地找到你想要定位的元素,并且有助于更快地执行样式重新计算。

DOM 的层级深度影响性能

总结

通过这个小实验,我得出了两个主要的结论。

第一个结论是现代浏览器非常了不起。它们能够在毫秒级的时间内解析、渲染和绘制一个包含数千层嵌套的DOM树,这简直令人难以置信。正如我们之前提到的,现实世界中并没有必要使用 5,000 层深度的 DOM,但看到浏览器(至少是我测试过的Chrome)能够优化处理这样的负载还是非常酷的。

第二个结论是它们可能没有昂贵的 JavaScript 操作那么具有影响力,但它们确实会产生差异(并且会迅速累积),因此值得密切关注。

作者:ikoofe

来源-微信公众号:KooFE前端团队

出处:https://mp.weixin.qq.com/s/ABuyPEnH88becSFqD-gL3w