浏览器匹配CSS选择器是从右往左,先找最右的.title元素,再逐层向上验证父级是否为.card、.content等,每多一层就增加一次DOM回溯,5层嵌套在低端设备上易致样式计算超20ms、CPU飙升90%+。

浏览器匹配选择器是从右往左,不是从上往下
写 .layout .main .content .card .title 这种 5 层选择器时,浏览器不会先找 .layout 再钻进去,而是:先找出所有 .title 元素,再对每个 .title 向上检查父级是否为 .card,再查该 .card 的父级是否为 .content……每多一层,就多一次 DOM 树向上遍历。DOM 节点一过 500 个(比如商品列表、评论流),Recalculate Style 阶段很容易超 20ms,滚动直接掉帧。
这不是理论值——低端 Android WebView 实测中,4 层就是硬性临界点;5 层以上,CPU 占用常飙到 90%+,element.classList.add("is-active") 失效、DevTools 里“已计算样式”被压在几十条灰色规则下,都是这个回溯链太长的直接表现。
& 只认最近一层父选择器,空格决定输出结果
& 不是“视觉缩进里的最外层”,而是上一层选择器的完整字符串拼接结果。有无空格,编译出的选择器天差地别:
- 有空格:
.card { .header { &__title { } } }→ 编译出.header__title(&指向.header,和.card完全无关) - 没空格:
.card { &__header { &__title { } } }→ 编译出.card__header__title(BEM 明确禁止这种跨层 Element) - 嵌套里再用
&:.card { .body { &__content { } } }→.body__content,HTML 里若没写class="body",这条规则就彻底不生效
误以为 & 是作用域保护,结果生成更重、更耦合的选择器,比如 .card .header .title.active,4 层 + 状态类,权重拉满,后续几乎无法靠单类名覆盖。
@at-root 不是逃逸键,它只适合提级本不该继承的规则
@at-root 解决的是“逻辑上该平级,却被嵌套裹挟”的问题,不是给 4 层缩进打补丁。常见误用:
- 错误:
.modal { .overlay { @at-root .tooltip { top: -10px; } } }→ 输出.tooltip✅(合理提级工具类) - 错误:
.btn { @at-root { &--large { font-size: 18px; } } }→ 编译失败 ❌(@at-root后必须跟完整选择器) - 危险:
@media (min-width: 768px) { .card { &__body { } } }→ 若外层还有.layout .dashboard,会产出 6 层选择器,完全失控
真正该提级的是工具类(如 .u-text-center)、媒体查询中的全局响应式逻辑、或兄弟组件样式。不该提级的是本就该属于当前语义层级的内容——那种情况,删掉嵌套比加 @at-root 更有效。
BEM 不是命名规范,是匹配路径切断协议
BEM 的核心价值,是让每个样式只依赖一个类名,彻底绕过浏览器向上查找父级的过程。它不是语法糖,是语义映射:
-
card__title必须显式出现在 HTML 中:✅ 匹配单类,零回溯 -
card__header__logo是反模式 ❌ 应拆成card__header和card__logo两个独立类 - 修饰符必须能独立存在:
button--loading不能依赖button类才生效,否则el.classList.add("button--loading")会失效
最容易被忽略的一点:BEM 生效的前提是 HTML 真实应用这些类名。光写 .card { &__title { } } 没用,如果模板里漏了 class="card__title",样式就永远不匹配——这不是 Sass 编译问题,是前后端协作断点。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











