真支持 :has() 需运行时双重验证:先 css.supports('selector(:has(div))'),再 document.queryselector('body:has(div)') 不抛错且返回非 null;ie11 需先判断 css?.supports 存在性,且测试须在 domcontentloaded 后执行。

直接判断浏览器是否真支持 :has(),别信 @supports 或构建工具的“自动处理”
很多项目用 @supports selector(:has(*)) 包裹样式,以为能安全降级——但 iOS 15.3、Firefox 120 及更早版本连这个 @supports 语法都不识别,整段 CSS 被静默跳过;而 cssnano 等压缩工具在检测到未知语法时,会直接删掉整条 :has() 规则,连 fallback 都没机会生效。真正可靠的检测必须运行时执行:
-
CSS.supports('selector(:has(div))')返回true是必要条件,但不是充分条件(旧 Chromium 90–104 有假阳性) - 必须配合 DOM 实测:
document.querySelector('body:has(div)')不抛错且返回非null才算真可用 - IE11 完全不支持
CSS.supports,需先做存在性判断:typeof CSS?.supports === 'function' - 检测代码必须在
DOMContentLoaded后执行,否则测试元素未挂载会导致误判
用 querySelector + classList 替代 :has() 的核心逻辑
:has(.error) 并不是“找一个带 .error 的子元素”,而是“当该父元素内存在一个渲染可见的 .error 元素时响应”。JS 替代不能只写 el.querySelector('.error'),还要排除 display: none 等隐藏状态:
- 用
el.querySelector('.error')?.offsetParent !== null判定是否真实可见(offsetParent为null表示不可见) - 对表单校验类场景(如
form:has(input:invalid)),应遍历所有input并调用i.checkValidity(),而非依赖伪类 - 避免写
el.querySelector('.child.active')这种双重 class 查询——class 切换有微小延迟,改用el.querySelector('.child')?.classList.contains('active')更稳 - 父元素必须有稳定标识(如
class="form-group"),禁用div:nth-child(2)这类结构脆弱的选择器
监听精准事件,而不是无脑用 MutationObserver
监听整个 document 的 DOM 变动开销大、易漏判、难清理。实际业务中,你关心的只是几个明确状态变化:
- 表单字段:监听
blur或change,再调用field.closest('.form-group')更新类名 - 复选框/单选框:监听
change(不是click),避免重复触发 -
<details></details>展开收起:监听toggle事件,比轮询[open]属性准确得多 - 动态插入内容(如评论列表):只监听目标容器的
childList,不监听subtree
引入 polyfill-pseudoclass-has 前,先确认它解决不了什么
这个 polyfill 确实能补全 querySelector、matches 等方法对 :has() 的支持,但它不监听 DOM、不自动更新、也不兼容 IE 或 Android 4.4 WebView。更重要的是:
- 它无法处理
:has(input:focus-visible)这类依赖运行时焦点状态的写法——焦点可能在 JS 执行间隙丢失 - 它对
:has(::before)、:has(:hover)、嵌套:has(:has(...))等非法组合也无能为力 - 即使 polyfill 加载成功,仍需保留基础 class 降级(如
.form-group.has-error),否则加载失败时样式彻底断裂 - 最易被忽略的是:当子元素靠
display: none隐藏时,:has()依然匹配成功,而 JS 方案反而更容易加offsetParent控制可见性逻辑
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











