bem 提升性能的核心在于强制使用单类名选择器(如.card__title--hovered),使浏览器能通过哈希表一次命中,避免从右往左匹配时的dom回溯;嵌套层级≥4时回溯成本激增,尤其在低端设备上耗时可高出4倍以上。

因为浏览器解析 CSS 选择器时,**必须从右往左匹配**,而 BEM 强制生成的单类名(如 .card__title--hovered)让浏览器能直接哈希查找,跳过所有 DOM 回溯。
浏览器匹配选择器的真实路径是“从右往左”
写 .card .card__title,浏览器不是先找 .card,而是:
- 先扫描页面中所有带 card__title 类的元素;
- 对每个这样的元素,逐层向上检查父节点是否含 card 类;
- 如果 DOM 深度大、同类元素多(比如长列表),这个回溯链会指数级拉长。
而 .card__title--hovered 是单个类名,浏览器直接查 class 属性的哈希表,一次命中,无回溯。
为什么嵌套层级 ≥4 就明显变慢
在低端 Android 设备上,.page .main .card .card__title 这种 4 层选择器的 “Recalculate Style” 耗时,可能比 .card__title 高 4 倍以上。DevTools Performance 面板里若看到该任务持续 >16ms,大概率就是这类选择器在拖后腿。
- 每多一层空格或
>,就多一次向上遍历父节点的操作 - :hover、:focus 放在最右边(如
p:hover)会触发全量扫描,成本比用修饰符高 2–3 倍 - 伪类嵌套(如
.card:hover .card__title)不是“BEM 写法”,它仍需运行时验证父级状态
常见错误:写了 BEM 名字,却没用 BEM 的写法
很多人以为类名带双下划线就是 BEM,结果 HTML 里还套三层 div,CSS 里继续写 .card .card__title——这等于把 BEM 当装饰,实际输出的仍是嵌套选择器,完全失效。
真正起作用的不是命名本身,而是最终 CSS 文件里是否全是单类名选择器。
- ✅ 正确:
.card__title { },HTML 中直接写class="card__title" - ❌ 错误:
.card .card__title { },哪怕 Sass 里用了&__title,只要编译后带空格就破功 - ⚠️ 注意:
grep -r "\.[a-z]\+ \.[a-z]" dist/可快速揪出残留的空格分隔选择器
修饰符和媒体查询怎么写才不破坏扁平性
BEM 允许有限嵌套,但前提是不引入运行时回溯依赖。关键看最终选择器是否仍控制在 ≤2 层、且作用域明确。
- 状态修饰符前置写法:
.is-open .modal__content是可接受的 2 层,比.modal.is-open .modal__content更易维护 - 媒体查询内允许 1 层嵌套:
@media (min-width: 768px) { .nav__item { display: flex; } } - 伪类必须转为修饰符:
.card--hovered .card__title(由 JS 切换card--hovered类),而非.card:hover .card__title - CSS-in-JS 场景可用
:where(.card) .card__title降权,不影响匹配性能
最容易被忽略的是:BEM 不防错,只管名字。哪怕只有一处 .list .item 残留,在长列表滚动或 hover 动画中,都可能成为帧率瓶颈的起点。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











