Shadow Dom渲染性能

译文链接
我的一个简短的回答是:能提高一点。虽然它可能还不足以在一般的 Web APP 中产生很大的差异,但我们值得去理解为什么它能提高样式性能。

首先,让我们回顾一下浏览器的渲染流程[2](rendering pipeline),以及为什么我们可以推测 shadow DOM 能提高样式性能。我们知道,浏览器渲染过程的两个基本部分是样式计算(style calculation)和布局计算(layout calculation),或者简称为“样式”和“布局”。前者的工作是:确定哪些 DOM 节点具有哪些样式(基于 CSS),后者则是:确定这些 DOM 节点实际放置在页面上的位置(使用上一步中计算出的样式)。

image.png

Chrome DevTools 中的性能跟踪,显示了基本的 JavaScript → 样式(Style) → 布局(Layout) → 绘制(Paint)流程

浏览器很复杂,但一般来说,页面上的 DOM 节点和 CSS 规则越多,运行样式和布局步骤所需要的时间就越长。提高此过程性能的一个方法是将工作分解为更小的块,即封装(encapsulation)——这是 shadow DOM 能提高样式性能的原因。

对于布局(Layout)的封装,我们有 CSS Containment[3]。这个在其他文章[4]里已经讲过了,这里就不赘述了。可以这么说,我认为有足够的证据表明CSS containement可以提高性能,所以如果你还没有尝试将contains: content 置放在 UI里面以查看它是否可以提高布局性能,那么你绝对应该去试一试。

至于样式(Style)的封装,我们有一些新奇玩意儿:shadow DOM[5]。就像 CSS Containment能提高布局性能一样,shadow DOM理论上 应该也能提高样式性能。让我们考虑一下原因:

什么是样式计算

如前文所述,样式计算不同于布局计算。布局计算与页面的几何布局有关,而样式计算则更明确地与 CSS 相关。这是采用如下规则的过程:

div > button {color: blue;}

还有一个 DOM 树:

<div><button></button></div>

...例子如上,我们可以确定<button>应该具有color:blue,因为它的父元素是 <div>。粗略地说,这是计算 CSS 选择器(在本例中为 div > button)的过程。

现在,在最坏的情况下,这是一个 时间复杂度为O(n * m) 操作,其中 n 是 DOM 节点的数量,m 是 CSS 规则的数量。(即遍历每个 DOM 节点和每个 CSS 规则,弄清楚它们是否相互匹配。)显然,浏览器不会这样做,否则任何大小合适的 Web 应用程序都会变得非常缓慢。浏览器在这方面有很多优化,这也是我们经常建议不要过分担心 CSS 选择器性能的部分原因。(请参阅这篇文章[6],了解该主题的最新处理)

也就是说,如果你使用过有着大量 CSS 代码量的代码库,你可能会注意到,在 Chrome 性能配置文件中,样式成本不是零。根据 CSS 的大小或复杂程度,你可能会发现实际上花在样式计算上的时间多于布局计算。因此,研究样式性能并不是完全没有价值的。

Shadow DOM 和样式计算

为什么shadow DOM 会提高样式性能?这里再说一次,是因为封装!如果你有一个包含 1,000 个规则的 CSS 文件和一个包含 1,000 个节点的 DOM 树,浏览器在运行前,并不会知道哪些规则适用于哪些节点。即便你使用 CSS Modules[7]、Vue scoped CSS[8] 或 Svelte scoped CSS[9] 来创作你的 CSS,最终你也只会得到一个隐式映射到 DOM 树的样式表,因此浏览器必须在运行时找出 CSS 和 DOM 树之间的联系(例如使用类或属性选择器)。Shadow DOM 则不同。使用 shadow DOM,浏览器不必猜测哪些规则适用于哪些节点——因为它就在 DOM 节点中:

<my-component>  #shadow-root<style>div {color: green;    }</style><div></div><my-component><another-component>      #shadow-root<style>div {color: blue;        }</style><div></div> </another-component></my-component></my-component>

在这种情况下,浏览器不需要针对 DOM 中的每个节点测试 div {color: green}规则——它知道它的范围是 <my-component><another-component> 中的div {color: blue}规则同上。理论上,这可以加快样式计算过程,因为浏览器可以依赖于通过 shadow DOM 的显式范围,而不是通过类或属性的隐式范围。

基准测试

这是理论,但当然,在实践中事情总是更复杂。所以我整理了一个 benchmark[10] 来衡量shadow DOM的样式计算性能。某些 CSS 选择器往往比其他选择器更快,因此为了获得不错的覆盖率,我测试了以下选择器:

  • id (#foo)
  • class (.foo)
  • attribute ([foo])
  • attribute value ([foo="bar"])
  • “silly” ([foo="bar"]:nth-of-type(1n):last-child:not(:nth-of-type(2n)):not(:empty))

粗略地说,我希望 idclass 选择器是最快的,其次是attributeattribute value选择器,然后是“silly”选择器(添加一些东西以真正让样式引擎工作)。

为了测量,我使用了一个简单的 requestPostAnimationFrame[11] 插件,它可以测量在样式、布局和绘制上所花费的时间。这是 Chrome DevTools 中正在测量的内容的屏幕截图(注意 Timings 部分下的“total”):

image.png

为了运行实际的基准测试,我使用了 Tachometer[12],这是一个很好的浏览器微基准测试工具。在这种情况下,我只取了 51 次迭代测试中的中位数。

基准测试创建了几个自定义元素,并且要么使用自己的 <style>shadow DOM “on”)附加一个 shadow root,要么使用具有隐式范围(shadow DOM “off”)的全局 <style>。通过这种方式,我想对 shadow DOM 本身和 shadow DOM “polyfills”(即不依赖于 shadow DOM 的 CSS 作用域系统)进行公平的比较。

每个 CSS 规则看起来像这样:

#foo {color: #000000;}

每个组件的 DOM 结构看起来像这样

<div id="foo">hello</div>

(当然,对于属性和类选择器,DOM 节点将有一个属性或类。)

基准测试的结果

以下是 Chrome 中 1,000 个组件和每个组件 1 个 CSS 规则的结果:

image.png 统计图1
id class attribute attribute-value silly
Shadow DOM 67.90 67.20 67.30 67.70 69.90
No Shadow DOM 57.50 56.20 120.40 117.10 130.50

如你所见,classesid 选择器在打开或关闭 shadow DOM 时大致相同(实际上,没有 shadow DOM 更会快一点)。但是一旦选择器变得更复杂( attributeattribute value“silly” 选择器),shadow DOM 就大致保持不变,而非 shadow DOM 版本时间消耗就更多。

如果我们将每个组件增加到 10 个 CSS 规则,我们可以更清楚地看到这种效果:

image.png 统计图2
id class attribute attribute-value silly
Shadow DOM 70.80 70.60 71.10 72.70 81.50
No Shadow DOM 58.20 58.50 597.10 608.20 740.30

上面的结果是针对 Chrome 的,但我们在 Firefox 和 Safari 中也看到了相似的数字。这是具有 1,000 个组件和 1 个 CSS 规则的 Firefox:


image.png 统计图3
id class attribute attribute-value silly
Shadow DOM 27 25 25 25 25
No Shadow DOM 18 18 32 32 32

Firefox 有 1,000 个组件,每个组件有 10 条 CSS 规则:

统计图 Firefox
id class attribute attribute-value silly
Shadow DOM 30 30 30 30 34
No Shadow DOM 22 22 143 150 153

这是带有 1,000 个组件和 1 个 CSS 规则的 Safari:

统计图 Safari-1
id class attribute attribute-value silly
Shadow DOM 57 58 61 63 64
No Shadow DOM 52 52 126 126 177

Safari 有 1,000 个组件,每个有 10 条 CSS 规则:

统计图 safari-2
id class attribute attribute-value silly
Shadow DOM 60 61 81 81 92
No Shadow DOM 56 56 710 716 1157

所有基准测试都在 2015 年的 MacBook Pro 上运行,每个浏览器都是最新版本(Chrome 92、Firefox 91、Safari 14.1)。

结论和未来的工作

我们可以从这些数据中得出一些结论。首先,shadow DOM 确实可以提高样式性能,所以我们关于样式封装的理论是成立的。然而,idclasses 选择器足够快,以至于实际上是否使用 shadow DOM 并不重要——事实上,没有 shadow DOM 时它们甚至会稍微更快一点。这表明像 SvelteCSS Modules 或优秀的老式 BEM[13] 这样的系统正在使用性能方面的最佳方法。

这也表明,与 classes 相比,使用 attribute 进行样式封装不能很好地进行扩展。所以,也许像 Vue 这样的框架,切换到 classes 会更好。

另一个有趣的问题是,为什么在所有三个浏览器引擎中,classesid 选择器在使用 shadow DOM 时都稍慢。这对浏览器供应商本身来说可能是一个更好的问题,我不会再进行推测。不过,我要说的是,这些差异的绝对值很小,我认为不值得偏向其中任何一个。数据中最清晰的信号是 shadow DOM 有助于保持样式成本大致恒定,而如果没有 shadow DOM,你可应该坚持使用简单的选择器,如 classesid,以避免遇到性能悬崖。

至于未来的工作:这是一个非常简单的基准测试,有很多方法可以扩展它。例如,基准测试每个组件只有一个内部 DOM 节点,并且它只测试平面选择器——没有后代或兄弟选择器(例如 div divdiv > divdiv ~ divdiv + div)。理论上,这些场景也应该有利于 shadow DOM,特别是因为这些选择器不能跨越 shadow 边界,所以浏览器不需要在 shadow root 之外寻找相关的祖先或兄弟姐妹。(尽管浏览器的 Bloom 过滤器[14]使这变得更加复杂 - 请参阅这些文档[15]以很好地解释这种优化是如何工作的。)

不过总的来说,我认为上述数字还没有大到足以让普通的网络开发者开始担心优化他们的 CSS 选择器,或者将他们的整个网络应用迁移到 shadow DOM。这些基准测试结果可能仅在以下情况下才有意义:1) 你正在写一个框架,因此你选择的任何模式都会被放大数倍,或者 2) 你已经对你的 Web 应用程序进行了分析,并且看到了大量高额的样式计算成本。但是对于其他人,我希望你能懂得:至少这些结果是有趣的,它揭示了一些 shadow DOM 工作的原理。

更新:Thomas Steiner[16] 也想知道标签选择器的测试结果(例如 div {}),所以我修改了基准测试[17]来测试它。我只会列出 Shadow DOM 版本的结果,因为基准测试使用 div,在非 shadow DOM 情况下,不可能单独使用标签来区分不同的 div。从绝对值来看,这些数字看起来非常接近 id 选择器和 classes选择器的数字(甚至在 Chrome 和 Firefox 中更快一点):

Chrome Firefox Safari
1,000 components, 1 rule 53.9 19 56
1,000 components, 10 rules 62.5 20 58

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356