&__content在块顶层也不总是安全,因&代表完整父选择器字符串(如.sidebar .widget),拼接后生成.sidebar .widget__item,浏览器只认.widget__item;必须用@at-root #{&}__element强制提取纯净块名生成扁平类名。

直接用 &__element 或 &--modifier 写在块顶层,看似简洁,但只要嵌套层级一深、或父选择器带空格,就立刻产出带空格的后代选择器(如 .card .card__header),根本不是 BEM 要的扁平类名。
为什么 &__content 在块顶层也不总是安全?
SCSS 的 & 永远代表“当前完整父选择器字符串”,不是“块名”。如果块本身是 .sidebar .widget,那么 &__item 编译出来就是 .sidebar .widget__item —— 浏览器只认 .widget__item 这个单类名,前面那个 .sidebar 不仅多余,还会让样式依赖 DOM 结构。
-
&无法自动剥离空格或提取第一个类名;extract(&, 1)在 Less 里勉强可用,但在 SCSS 中不原生支持 - 即使写成
.card { &__header { } },也只在父选择器是单类(如.card)时才侥幸正确;一旦写成.container .card { &__header { } },结果就是.container .card .container .card__header - 所有 BEM 元素和修饰符必须语义上归属块本身,而不是其 DOM 父容器
@at-root #{&}__element 是目前唯一可预测的写法
它强制跳出嵌套上下文,在 CSS 根层级拼接出纯类名,绕过 SCSS 默认的后代关系展开逻辑。关键点不在 @at-root 本身,而在于 #{&} 插值能取到当前作用域下最简块名(前提是块定义本身是单类)。
- 必须确保
&所在的作用域是干净的块级定义,例如:.button { @at-root #{&}__icon { ... } }→.button__icon - 禁止在
&:hover、&[disabled]、.button__icon等子选择器内部再调用@at-root #{&}__xxx,否则&值已污染,插值结果不可控 - 若块名来自变量(如
$block: "input"),直接用@at-root #{$block}__label更稳妥,不依赖&
用 @mixin 封装时,& 的绑定时机决定一切
一个叫 bem() 的万能 mixin 很危险:它无法约束调用顺序,也无法保证 & 始终指向块名。真正可行的是三个分离的 mixin,每个只做一件事,并严格限定使用位置。
-
@mixin b($name)必须最先执行,它定义命名空间并设置&的初始值(如.#{$namespace}-#{$name}) -
@mixin e($name)和@mixin m($name)内部必须用@at-root #{&}__#{$name}和@at-root #{&}--#{$name},且只能在b()的@content中调用 - 分隔符(
__、--)必须抽成变量统一管理,避免硬编码散落各处 - 禁止在
e()内部再调用m(),BEM 不允许.button__icon--large这种嵌套修饰符
最常被忽略的一点:BEM 的解耦效果从不来自 SCSS 语法糖,而来自 HTML 中是否真实存在对应类名。哪怕 SCSS 编译出完美的 .card__title,如果模板里漏写了 class="card__title",样式就彻底失效——这种问题比编译错误更难定位,因为它不报错,只静默不生效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











