正文的方向没问题,但在真实项目里能把 Recalculate Style 抓到几十毫秒的,通常不是"后代选择器太长",而是 :has() 和通配属性选择器造成的失效范围放大——这两处的优先级比第 2 节里那几条要高得多。

先说匹配成本的真实排序:关键选择器是 class/id 时最便宜(Blink 有 id/class 索引和 bloom filter,不匹配的子树直接整棵剪掉),标签名次之,带 ^= * $= |= 的属性选择器最贵,只能线性比对字符串。但这个成本是"单条规则 × 规则总数",几百条规则下你根本测不出差异。真正炸的是失效计算:body:has(.modal-open) .wrap * 这种写法,改一个 class 会让浏览器把整篇文档的样式标记为可能失效重算。顺带纠正一点,> 和空格在匹配阶段都是往上回溯一层、成本一样,差的是失效传播范围。

补上你没写完的测量部分,Chrome 现在有现成的工具链:Performance 面板右上角齿轮勾选 Enable CSS selector stats,录一段后切到 Selector Stats 标签,按 Elapsed time 排序,能直接看到每条选择器的 match attempts / matches / 耗时,命中率低的超长链一目了然。再配合命令面板(Cmd/Ctrl+Shift+P)里的 Coverage 看未使用 CSS 体积,document.querySelectorAll('*').length 确认 DOM 规模。

如果让我排优化顺序:先砍 DOM 节点数 → 再砍规则总数 → 干掉 @import(串行阻塞,比选择器写法影响大得多)→ 最后才轮到选择器扁平化。把第四步的测量做完再动手,不然容易白改。

一个坑::where() 权重为 0 确实好用,但用它包住带 :has() 的复杂链并不能省掉失效成本,别在 :has() 里放会高频切换的 class。