浏览器css选择器匹配从右往左回溯,层数越多、最右元素越多则性能越差;bem通过扁平class名实现零回溯,是解决该问题的有效手段。

后代选择器匹配是“从右往左”查,不是顺着HTML往下找
浏览器解析 .sidebar .widget ul li a 时,并不会先找 .sidebar,再进里面找 .widget……它干的是相反的事:先抓页面里所有 a 元素(可能几百上千个),然后对每个 a 向上逐层检查父节点是不是 li、再是不是 ul、再是不是 .widget……直到根。DOM 越深、节点越多,这个回溯链就越长,CPU 就越忙。
4 层就是性能临界点,不是建议,是实测掉帧阈值
在低端 Android WebView 或低配大屏终端上,.page .main .content .card .title 这种 5 层选择器,Recalculate Style 阶段很容易超 20ms——滚动直接掉帧。这不是理论推演,是 Chrome DevTools 录制 Performance 面板后看到的真实耗时峰值。关键不在于“写了多少层”,而在于“浏览器要为多少个最右元素做回溯”:
-
a、span、strong这类标签太泛,一旦出现在大文本或列表中,候选集爆炸 -
[class~="btn"]或[data-role]比纯 class 慢,但比*或:not()安全得多 - 用
document.querySelectorAll(".card .title")测一下,返回节点数 > 300 就该警惕
嵌套越深,缓存越失效,重绘越频繁
浏览器没法有效缓存 .a .b .c .d 这类选择器的匹配结果。每次 JS 动态增删 class、插入新节点、甚至切换暗色模式,都得重新跑一遍完整回溯。更麻烦的是:
- 框架里写
.row .col-6 .card .card-body .btn,哪怕全是 class,只要 DOM 套得深,匹配开销就实打实存在 - 搭配
transition: all或box-shadow,会放大卡顿——匹配慢 + 绘制重,双重惩罚 -
* { box-sizing: border-box }看似方便,但在万级节点页首次解析时,会拖慢首屏
BEM 不是命名规范,是切断回溯的硬手段
.card__title 能直接命中目标元素,浏览器查 class 索引表就行,零回溯。它不是“更好看”,而是让渲染引擎少做几轮 DOM 遍历:
- 必须显式写在 HTML 里:
<h2 class="card__title"></h2>,不能靠结构推导 - 禁止
.card__header__logo这种跨层 Element,应拆成两个独立 class - 修饰符如
.button--loading必须能单独生效,不依赖基类
真正卡住的,往往不是“要不要用 BEM”,而是把 .card { .title { ... } } 编译出 .card .title 还浑然不觉——那不是嵌套语法问题,是选择器语义失控的开始。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











