:hover和:focus不能替代class控制状态,因伪类依赖用户输入、不具确定性,触摸设备可能不触发,键盘焦点不可达,js无法可靠检测,且disabled属性变更时样式易失效。

直接删掉嵌套四层以上、带多个 :not() 或 :focus-visible 的选择器,比调任何构建插件都快;伪类不是用来堆逻辑的,是给已有结构“加状态钩子”的。
为什么 :hover 和 :focus 不能替代 class 控制状态
很多人试图用 a:hover:not(.disabled) 替代 .btn--hoverable:not(.disabled),结果发现:鼠标移入瞬间样式闪动、键盘焦点无法触发、测试难断言。因为伪类依赖用户输入事件,而 class 是确定性状态标识。
- 伪类匹配由浏览器实时计算,
:hover在触摸设备上可能不触发,:focus在无 tab 键操作时不可达 - JS 检测伪类状态极不可靠(
getComputedStyle(el).getPropertyValue('color')不反映动态值) - 组件库中若用伪类控制“禁用态”,会导致 disabled 属性变更后样式延迟或失效
什么时候该用 :nth-child() 而不是加 class
仅当样式规则完全由 DOM 位置决定、且结构稳定不变时才用 :nth-child()。比如表格行斑马纹、固定数量导航项对齐。
- ✅ 安全场景:
tr:nth-child(odd) { background: #f9f9f9; }—— 表格结构由后端渲染,不会插入额外<tr> <li>❌ 危险场景:<code>.list-item:nth-child(2n)—— 若前端 JS 动态插入广告<li class="ad">,所有偶数项错位 - 性能提示:
:nth-child(3n+1)比:nth-of-type()计算开销略高,但差异微乎其微;真正拖慢的是nav ul li a:hover::before这种 5 层链式写法 -
:where(.btn, .link, .tag):hover特异性为 0-0-0,可被任意 class 规则覆盖,适合写默认交互基线 -
:is(.card-header, .modal-title) + * { margin-top: 0; }把原来 3 条独立规则压成 1 条,且不提升权重 - 注意:
:is()内部任一选择器无效(如语法错误),整个:is()失效;:where()则忽略无效项,更健壮 - 浏览器对
:not()的优化远弱于肯定匹配,Chrome 中深度 > 2 的:not()链会显著增加样式计算时间 - 真实项目中,80% 的
:not()可用 class 正向表达:把section:not(.compact)改成section.section-default,再用变量控制 - 若必须否定,优先用属性选择器:
[data-status="active"]比:not([data-status="inactive"])更快、更直观
如何用 :is() 和 :where() 降权并收口选择器深度
CSS :is() 和 :where() 是现代项目精简冗余的关键,尤其适合替换老式多 class 组合。
:not() 嵌套和连用是性能与维护双坑
像 div:not(.skip) > p:not([data-loaded]) > span:not(.hidden) 这类写法,不仅难读,还会让浏览器反复遍历节点树判断否定条件。
重构伪类不是追求“更炫的写法”,而是把状态归属还给 HTML 结构本身——class 定义“它是什么”,伪类只负责“它此刻怎么反应”。最常被忽略的一点:伪类规则一旦写进全局 CSS,就再也无法被 JS 精确开关,除非你愿意用 el.classList.toggle('force-hover') 这种反模式硬塞。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











