组合选择器一旦和 :not 放在一起,最容易被忽略的就是空格。.card:not(.featured) 和 .card :not(.featured) 只差一个空格,前者表示卡片本身不具备 featured 类,后者则表示卡片内部所有不具备 featured 类的后代元素。类似这种细微差异会直接造成样式覆盖范围扩大,排查起来相当费时。可以先把 :not 看成一个过滤条件,它必须紧贴在某个元素选择器上才会作用于该元素,空格、大于号、加号等组合器则负责切换过滤作用的主体。

先分清 :not 的参数规则
旧版规范里,:not 括号内只能写一个简单选择器,例如类选择器、属性选择器、标签选择器或 ID 选择器,不能写 .a .b 或 .a > .b 这种带组合器的结构,也不能直接传入多个并列参数。较新的 Selectors Level 4 规范允许 :not 接收选择器列表,比如 :not(.a, .b),这确实能减少重复代码,但旧浏览器可能存在不识别整条规则的情况。因此多参数写法最好用于增强层,基础样式仍然要单独保留。
另一个容易误解的地方是 :not 内部允许嵌套。像 :not(:not(.active)) 这在语法上相当于 .active,实际开发中几乎没有必要,反而会干扰阅读。真正需要组合排除条件时,优先考虑把不同的状态放在 :not 参数列表里,而不是层层嵌套。下面这段示例展示了合法与非法写法,注意后代组合器不能出现在 :not 内部。
/* 合法:简单选择器 */
.card:not(.disabled) {
display: block;
}
/* 合法:属性选择器 */
input:not([readonly]) {
background: #fff;
}
/* 较新规范:选择器列表 */
button:not(.primary, .ghost) {
border-color: #ccc;
}
/* 非法::not 内不能包含后代组合器 */
.list :not(div p) {
color: red;
}
空格和大于号决定排除作用在谁身上
在组合选择器里,空格和大于号最容易改变 :not 的作用对象。拿 .card :not(.title) 来说,它不是排除 .card 中带有 title 类的元素,而是先选中 .card 内的所有后代元素,再从里面剔除 class 为 title 的节点。也就是说,这个选择器最终命中 .card 内大量不带 title 的元素,包括段落、行内容器和链接等,范围非常宽。如果只想排除直接子元素中的 title,应该写作 .card > :not(.title)。
反过来,如果需求是当卡片自身不是 featured 时修改标题颜色,就要写 .card:not(.featured) .title。这时 :not 紧贴 .card,排除的是卡片节点,空格后的 .title 才是实际要设置样式的目标。两种写法结果完全不同,调试时可以先从选择器最右侧开始读,判断最终目标元素是谁,再反向检查排除条件落在哪一个节点上。
还有一个常见问题是把 :not 放在通配符前面,比如 .list :not(*) 或者 .list *:not(.item)。这类选择器虽然能被解析,但匹配范围比预期大,容易把大量后代元素都卷进来。更推荐明确写出标签或类,例如 .list > li:not(.item),这样既能缩小范围,也便于后续维护。
/* 范围太大:命中 card 内所有不带 title 的后代 */
.card :not(.title) {
margin: 0;
}
/* 精确:只命中 card 的直接子元素中不带 title 的节点 */
.card > :not(.title) {
margin-bottom: 12px;
}
/* 排除的是 card 自身 */
.card:not(.featured) .title {
color: #666;
}
兄弟选择器与多参数 :not 的配合
排除逻辑经常和结构伪类、兄弟选择器共同出现,例如导航项之间需要统一样式,但第一项或最后一项不参与。可以用 .nav-item:not(:first-child) 给除第一项以外的所有导航项设置左边距,再配合相邻兄弟选择器 .nav-item + .nav-item 也能达到相似效果。区别在于 :not(:first-child) 不依赖相邻关系,更适合中间有分隔线、边框或背景的场景。
当需要排除多个状态时,多参数 :not 比写多条选择器更直观。例如按钮有 primary、ghost、danger 三种强调样式,想给普通按钮设置边框,可以写 button:not(.primary, .ghost, .danger)。但要注意这条规则在旧版 Safari 或部分旧内核里会整条失效,所以通常先写 button 的基础样式,再把特殊样式覆盖回去,或者用 @supports selector(:not(.a, .b)) 做特性检测。
此外,:not 配合属性选择器可以过滤表单控件,比如 input:not([type=submit], [type=button]) 能统一设置文本输入类控件的背景,而不会影响按钮。这种写法比逐个列举 input[type=text]、input[type=email] 更省事,前提是项目浏览器支持列表参数。
/* 排除第一个导航项 */
.nav-item:not(:first-child) {
border-left: 1px solid #eee;
}
/* 多参数排除多种按钮状态 */
button:not(.primary, .ghost, .danger) {
border: 1px solid #ccc;
background: #fff;
}
/* 属性选择器组合 */
input:not([type=submit], [type=button]) {
padding: 8px;
border: 1px solid #ddd;
}
优先级与性能不能只看表面
:not 本身不会给选择器增加权重,但它的参数会参与特异性计算。比如 .card:not(#primary) 的特异性包含一个 ID 选择器,整体权重远高于普通的 .card 类选择器,后续想用 .card.special 覆盖可能都无效。这正是很多样式覆盖失败的原因:开发者以为 :not 只是个条件,忽视了括号里的 ID 或多个类带来的权重提升。
性能方面,现代浏览器对 :not 的优化已经很好,但在大型页面里仍要避免过宽的选择器。例如 body :not(.skip-to-content) 会让浏览器在 body 下的所有元素上评估排除条件,远不如把范围限制在 .site-header :not(.skip-to-content)。同样,尽量把 :not 和具体的类、标签或属性组合,而不是和通配符、深层后代随意拼接。这样既减少匹配计算,也能让选择器含义更清晰。
如果确实需要大面积排除,可以考虑反向策略:先给大部分元素设置统一规则,再对少数需要排除的元素单独覆盖。这个思路在无法使用多参数 :not 的旧环境里尤其有用,也能让样式层级更扁平。归根结底,:not 是提高表达力的工具,但组合选择器的范围控制仍然要由书写者把控。