如果你使用Lighthouse来衡量你网站的性能,你可能之前已经看到过避免过大的DOM尺寸的警告。它看起来像这样:
Lighthouse警告我们关于大DOM尺寸的问题,因为它们会增加内存使用量,并可能产生昂贵的样式计算。结合你网站上发生的所有其他事情,这可能会对用户体验产生重大影响,特别是对于低端设备的用户。
前几天,当我阅读我的网站的性能报告时,这个警告引起了我的注意。但不是我再次审视的DOM元素总数;而是下面报告的度量标准—最大DOM深度。
当我在写上周的通讯时,花了很多时间思考树(数据结构,不是森林中生长的那些),树的深度与时间复杂性之间的关系仍然记忆犹新。所以在Lighthouse报告中看到这个度量标准立即引发了一个问题:
🤔 DOM深度如何影响渲染性能?
当我们使用像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。我们知道理论上树越深,速度就越慢。但实际上这对性能有什么影响呢?
让我们做一个小实验来找出答案。
一个小实验
这里报告的所有指标都来自于在M1 Max MacBook上使用Google Chrome 125(无痕模式)运行性能配置文件的结果(4x CPU减速)。你可以在GitHub上查看测试页面的源代码。
为了测试这一点,我整理了几个只包含三行文本和100个空div的HTML页面。两个页面之间唯一的区别是,一个页面的所有div直接在文档的body中,而在另一个页面上,div是嵌套的。
所以,具有浅层元素树的那个看起来像这样:
<html>
<body>
<div></div>
<div></div>
<div></div>
<div></div>
<!-- 95个div之后... -->
<div>这是100个div中的最后一个。</div>
</body>
</html>
而具有深层树的那个看起来像这样:
<html>
<body>
<div>
<div>
<div>
<div>
<!-- 95个div之后... -->
<div>这是100个div中的最后一个。</div>
</div>
</div>
</div>
</div>
</body>
</html>
在第一页上运行性能配置文件,我得到了一个总的页面加载时间(解析+渲染+绘制)为51毫秒。而在第二页上,页面加载时间是惊人的……53毫秒。
所以,这有点令人失望。
当然,这不是一个非常有意义的示例,因为所有的div都是空的,浏览器几乎只是在屏幕上渲染了几行文本,但我仍然期望看到更大的差异。
幸运的是,并且多亏了编程的魔力,测试更多的div只需要敲几下键盘。所以我测试了200个,然后是300个,然后是400个。在500个div时,性能有了明显的差异。
性能报告加载页面,页面中有500个嵌套的div,用了100多毫秒。
浅层示例在56毫秒内渲染了所有500个div—基本上不受它需要处理的HTML量增加5倍的影响。但现在嵌套树需要102毫秒—几乎是两倍。
所以我继续测试,测试了多达5,000个div。这是结果:
值得注意的是,具有浅层树的页面和具有嵌套树的页面在大小上是相同的。它们都包含相同数量的元素,并且在浏览器中呈现时看起来完全相同。
唯一的区别是DOM树的深度,正如我们在上面图表中看到的,它确实对渲染性能有影响。
现在,你可能会想,考虑5,000的DOM深度是荒谬的事情,因为没有任何真正的网站会那么糟糕……你绝对是对的。
但公平地说,5,000个总元素并不是真的那么罕见,而且在真正的网站上,所有这些元素都不会像浅层树示例中那样处于相同的深度级别。
即使在相对较低的深度32,解析、渲染和绘制这么多元素仍然需要几百毫秒—而这一切都是在CSS和JS介入并使事情变得更糟之前。
加上一点CSS
说到CSS,大型和深层DOM树可能有问题的一个原因是它们为昂贵的样式重新计算打开了大门。
如果我们以我们的5,000个嵌套div为例,并向每个div添加一些文本,我们可以通过添加一个单一的CSS规则来测试样式重新计算的影响:
div {
padding-top: 10px;
}
由于这个规则几乎影响到页面上的每个元素,浏览器需要花费很长时间来确定我们每个div的更新位置。这种昂贵的样式重新计算任务可能会阻塞主线程,使我们的网站无响应。
昂贵的样式重新计算影响页面上的几乎每个元素。
我们在这个实验中只测量页面加载性能,但请记住,用户在初始加载后也会与你的页面进行交互。
这就是为什么关注DOM大小和深度如此重要。不是因为它们会使你的网站加载变慢,而是因为它们为所有其他运行时操作(例如作为用户交互的一部分通过JavaScript更新DOM)设定了基线。
那我们该怎么办呢?
主要的事情是定期检查你的DOM大小和深度。我知道这听起来很明显,但由于我们通常使用UI组件或模板部分,它们一次只向我们展示一点HTML,很容易忘记这些标记片段是如何累积的。
你可以使用Lighthouse或PageSpeed Insights这样的工具来测量你网站上的DOM大小和深度。如果你想快速检查页面上任何时候有多少元素,你可以在浏览器的控制台上运行这个:
document.querySelectorAll("*").length;
另一个有很大帮助的事情是减少你的CSS选择器的范围和复杂性。这使得浏览器更容易找到你试图定位的元素,并帮助更快地执行样式重新计算。
2017年的一项研究显示,大型DOM大小对转化率的影响的统计数据。
总结
我从这个小实验中得到了两个主要的收获。
第一个是现代浏览器是惊人的。它们能够在几毫秒内解析、渲染和绘制一个嵌套数千层的DOM树,这绝对是令人难以置信的。
正如我们之前提到的,没有真正的用例需要5000层深度,但看到浏览器(至少是我测试的Chrome)已经优化以处理负载,这真的很酷。
第二个收获是DOM大小和DOM深度对网站性能的影响比我最初想象的要大,特别是当它们与昂贵的样式重新计算结合时。
它们可能不像昂贵的JavaScript操作那样有影响力,但它们确实有所不同(而且它们很快就会累积),所以值得关注它们。
关于这个主题的更多信息,请查看这些资源:
文章的翻译到此结束。由于篇幅较长,我提供了逐段的翻译,以确保翻译的准确性和可读性。如果你有任何其他部分需要翻译,或者需要对特定段落进行更深入的解释,请随时告诉我。