BEM 的性能优势源于单类名选择器避免 DOM 回溯,而非命名规范本身;错误嵌套(如 .card .card__title)和伪类滥用(如 p:hover)会破坏该优势,需全程保持选择器扁平、无空格、无标签/属性选择器。

BEM 本身不提速,真正起作用的是它强制生成的单类名选择器,让浏览器跳过从右往左的 DOM 回溯过程。
浏览器 CSS 匹配是从右往左的,不是从左往右
写 .card .content p:hover,浏览器不是先找 .card,而是先扫描所有 p:hover 元素,再逐个向上检查:它的父级是不是 .content?再往上是不是 .card?DOM 越深、元素越多,这个回溯链就越长。
而 .card__content--hovered 这种单类名,浏览器直接查 class 属性哈希表,一次命中,没有回溯。
- ≥4 层嵌套在低端 Android 设备上,
Recalculate Style耗时可能比单类名高 4 倍以上 - DevTools Performance 面板里看到 “Recalculate Style” 持续 >16ms,大概率是这类选择器在拖后腿
-
:hover、:focus放在最右边(如p:hover)会触发全量扫描,比用修饰符.p--hovered多出 2–3 倍匹配成本
常见错误:写了 BEM 名字,却没用 BEM 的写法
很多人以为只要类名带双下划线就是 BEM,结果 HTML 里还套着三层 div,CSS 里继续写 .card .card__title——这等于把 BEM 当装饰,实际输出的仍是两层嵌套选择器。
立即学习“前端免费学习笔记(深入)”;
- ✅ 正确:
<h2 class="card__title"></h2>+.card__title { } - ❌ 错误:
<div class="card"> <h2 class="card__title"> + <code>.card .card__title { } - Sass 中混用
.card { &__title { } }和.card &__title,后者会编译出空格选择器 - 构建后用
grep -r "\.[a-z]\+ \.[a-z]" dist/能快速揪出残留的空格分隔选择器 - 不要写
.card:hover .card__title,改用 JS 切换.card--hovered,再写.card--hovered .card__title—— 这仍是 2 层,且可控 - 媒体查询内允许安全嵌套 1 层:
@media (min-width: 768px) { .header__logo--large { } } - 慎用
:where(.card) .card__title:虽能降权,但部分旧浏览器不支持,且掩盖了真实选择器结构 - CSS-in-JS 中若动态插入属性选择器(如
[data-id="123"] .button),会直接废掉 BEM 的解析优势
伪类、媒体查询和状态修饰符怎么写才不破功
加 :hover 或 @media 不是退回到嵌套的理由。BEM 的性能优势,只在你保持选择器扁平的前提下成立。
真正难的不是命名,而是让每个选择器都经得起 Elements → Computed → Styles 面板里的一眼验证:它是不是单类名?有没有空格?有没有意外混入的标签或属性选择器?这些细节不检查,BEM 就只是名字长了点的普通 CSS。



















