BEM本身不提速,真正起效的是强制使用单类名选择器(如.card__title),使浏览器能通过哈希表一次命中,避免从右往左匹配时的DOM回溯;嵌套≥4层时回溯成本激增,低端设备耗时可高出4倍以上。

因为BEM本身不提速,真正起效的是它强制你写出的单类名选择器(如 .card__title),让浏览器跳过从右往左的 DOM 回溯过程,直接哈希查找一次命中。
浏览器匹配 CSS 时永远从右往左
写 .card .card__title,浏览器不是先找 .card,而是先扫描所有带 card__title 类的元素,再逐个向上检查:它的父级有没有 card?这个回溯链在长列表、深 DOM 或悬停动画中会急剧放大。而 .card__title 是单类名,查 class 属性哈希表,无回溯。
- ≥4 层嵌套在低端 Android 设备上,
Recalculate Style耗时可能比单类名高 4 倍以上 - DevTools Performance 面板里看到 “Recalculate Style” 持续 >16ms,大概率是这类选择器在拖后腿
-
:hover、:focus放在最右边(如p:hover)会触发全量扫描,比用修饰符.p--hovered多出 2–3 倍匹配成本
Sass 或其他预处理器容易悄悄破坏 BEM 扁平性
很多团队以为写了 &__title 就安全,但一不小心就产出带空格的选择器。比如:
-
.card { &__content { &:hover { } } }→ 编译成.card .card__content:hover,空格 + 伪类双重开销 -
.card { & .card__badge { } }→ 编译出.card .card__badge,直接退化为嵌套 -
@at-root (without: rule)可能意外把修饰符拖进全局作用域,导致权重失控
构建后用 grep -r "\.[a-z]\+ \.[a-z]" dist/ 能快速揪出所有残留的空格分隔选择器。
立即学习“前端免费学习笔记(深入)”;
哪些写法看似合理,实则破功
人眼觉得“结构清晰”,浏览器却要多走几层路:
-
.card:hover .card__title❌ —— hover 状态需运行时验证父级,应由 JS 切换.card--hovered,再配.card--hovered .card__title(虽含空格,但深度可控、可预测) -
@media (min-width: 768px) { .card .card__title { } }❌ —— 应改用.card__title--lg这类修饰符,媒体查询内只允许 1 层安全嵌套(如@media { .nav__item { } }) -
div.card__title或[data-id="123"] .button❌ —— 标签选择器和属性选择器会废掉 BEM 的哈希查找优势
真正难的不是命名,而是每次修改 CSS 后都去 DevTools → Elements → Computed → Styles 面板里确认:那条规则的选择器是不是纯 .xxx?有没有空格?有没有意外混入的标签或属性?这些细节漏查一次,BEM 就只是名字长了点的普通 CSS。



















