直接写&__content会编译出.card .card__content(带空格的后代选择器),违背BEM扁平要求;正确做法是用@at-root #{&}__content或手动写全名.card__content。

为什么直接写 &__content 会编译出错误选择器
因为 & 只是字符串替换,不是“智能前缀生成器”。.card { &__content { } } 实际编译为 .card .card__content(中间有空格),变成后代选择器,违背 BEM 扁平类名要求。这不是 bug,是 Sass 嵌套的默认行为——空格照单全收。
- VS Code 自动补全或粘贴时容易混入不可见空格:
&__content和& __content(后者带空格)输出完全不同,后者必失效 - 所有 BEM 元素(
__)和修饰符(--)必须直属于块名,不能跨层嵌套,比如.card__header__title违反 BEM 原则,应拆成独立块或改用.card__header-title - 真正安全的写法只有两种:
@at-root #{&}__content强制提级,或手动写全名.card__content
&--modifier 必须和块同级,不能塞进伪类里
修饰符语义上是块的独立状态,不是某个状态下的子类。.btn { &:hover { &--active { } } } 编译出 .btn:hover--active,浏览器直接忽略——非法类名,且无法与其他修饰符组合(如 .btn--primary.btn--disabled)。
- 正确写法是把修饰符提至块顶层:
.btn { &--primary { } &--disabled { } }→ 输出.btn--primary、.btn--disabled - 如果需要“悬停时图标变色”,应写
.btn__icon { @at-root .btn--hover & { } },而非在&:hover内部再嵌套&--spin - 元素级修饰符也必须扁平:
&__icon--spin,不是&__icon { &--spin { } }
嵌套超 3 层就该停手,否则选择器权重爆炸
浏览器匹配选择器是从右往左查的。.layout .sidebar .nav .item a 这种 5 层后代选择器,权重升到 0-5-0,Lighthouse 直接报 “Avoid overly specific selectors”,而且后期覆盖困难,!important 越来越多。
- Sass 官方推荐嵌套上限是 3 层:容器 → 组件 → 状态/伪类(如
&:hover、&--active) - 用 VS Code 按住 Ctrl 点击编译后的 CSS 文件,直接看生成结果——别靠缩进猜
- 想保留外层上下文但又不想多层
&?用变量暂存:$block: ".card"; #{$block}__title { color: red; } - 禁用 ID 选择器:
#header { .nav { } }编译后是#header .nav(权重 0-101-0),后面加 10 个 class 都压不住
用 @use 隔离模块,别靠嵌套假装封装
单纯缩进写 .card { .title { } } 看似封装,实则编译出 .card .title,依赖 HTML 结构层级。一旦 .title 被挪到卡片外,样式立刻失效;全局若有同名 .title,还可能互相干扰。
立即学习“前端免费学习笔记(深入)”;
- 真正的作用域封装得靠
&显式绑定:.card { &__title { } }→.card__title,不依赖 DOM 深度 -
@use替代@import:变量、mixin 默认隔离,必须加命名空间调用,比如base.$primary-color,避免静默覆盖 -
@use必须放在文件最顶部,且同一个模块在单个文件中只应@use一次 - 避免在 Sass 中滥用
:global()—— Sass 本身不支持,混用 css-loader 会导致 IDE 失去提示、重构找不到引用点
&,都得问一句:它最终拼出的是扁平类名,还是带空格的后代选择器?编译后的 CSS 才是运行时依据,不是源码缩进。


















