浏览器匹配CSS选择器是从右往左,BEM通过单类名实现哈希查找、避免回溯,强制显式编码模块关系,禁用标签/ID/属性选择器,仅允许媒体查询、伪类和主题切换三类安全嵌套。

浏览器匹配CSS选择器是从右往左的,不是从左往右
很多人以为写 .card .card__title 是“先找 .card 再找里面 .card__title”,实际浏览器干的是:先找出所有 .card__title 元素,再逐个往上检查父级是不是 .card。DOM 越深、同名元素越多,回溯路径越长——4 层嵌套如 .layout .sidebar .widget h3 在低端设备上 Recalculate Style 时间可能比单类名慢 4 倍以上。
而 BEM 的 .card__title 是单类名,浏览器直接哈希查找,一次命中,不回溯。
BEM 把 DOM 层级关系“翻译”成类名结构,而非依赖真实嵌套
传统写法靠 HTML 结构隐式表达关系:<div class="card"><div class="body"><h3> → 用 .card .body h3 定位。一旦结构微调(比如加一层 wrapper),样式就失效,还很难发现。
BEM 强制显式编码关系:.card__title 表示“这是 card 模块下的 title 元素”,跟它在 DOM 里嵌了几层无关。哪怕你把 <h3 class="card__title"> 直接扔到 body 最外层,样式依然生效——这不是 bug,是设计意图。
立即学习“前端免费学习笔记(深入)”;
常见错误包括:
- 写
.card__header__logo:BEM 不允许跨层 Element,说明结构过深,应拆出新 Block(如.logo) - 用
.card > .title:仍依赖层级,违背 BEM “扁平选择器”原则 - 给 Block 起泛称名如
box或带位置词如top-banner:导致复用时语义断裂,被迫加嵌套救火
所有样式必须由类名驱动,禁用标签/ID/属性选择器组合
BEM 规范下,button.card__submit、#submit-btn、[data-role="submit"] 都不被允许。因为:
- 标签选择器(
button)权重低但污染全局,div ul li a这种写法会强制全量扫描 - ID 选择器权重过高(0,1,0,0),后续覆盖只能靠
!important或更长选择器,主动放弃维护性 - 属性选择器(
[type="text"])让浏览器放弃哈希索引,退化为字符串比对
真正合规的写法只有:.card__submit、.card__submit--disabled、.card__submit--loading —— 权重恒为 0,1,0,可预测、易覆盖、不耦合结构。
嵌套只允许出现在媒体查询、伪类、主题切换这三类场景,且深度 ≤2
完全不用嵌套不现实,但必须收口。BEM 允许的安全嵌套仅限于:
- 媒体查询:
@media (min-width: 768px) { .card__title { ... } } - 伪类状态:
.card__submit:hover、.card__submit:focus(注意:伪类必须放在最右边,否则触发全量扫描) - 主题切换:
.theme-dark .card__submit,但要求.theme-dark是根级 class,不层层透传
关键限制:
- 禁止在 Block 内部用
.card .card__title这类写法,哪怕只写一次,也会让整条规则对 DOM 结构产生隐式依赖 - Sass/Less 中的
&__elem是生成扁平类名的语法糖,@at-root不是用来“拯救深层嵌套”的,而是用于媒体查询或命名空间隔离 - 工具类(如
u-text-center)可与 BEM 并存,但不能混进 Block 内部结构,比如别写.card__title u-text-center
真正难的不是起名字,是克制住“用嵌套表达关系”的本能——BEM 的约束感,恰恰来自它拒绝替你做决定。


















