:first-child常失效是因为它要求目标元素必须是父元素第一个子节点且标签名匹配,而换行、注释等会生成文本或注释节点使其失去首位;:first-of-type则跳过非同类节点,只在同标签兄弟中取首个,抗干扰更强。

为什么 :first-child 经常“选不到”第一个 p 或 li
因为它只认 DOM 子节点的绝对位置:目标元素必须是父元素子节点列表中索引为 0 的那个,且标签名要匹配。哪怕源码里只写了一行 <p>内容</p>,只要前后有换行、空格或 HTML 注释,浏览器就会生成 #text 或 <!-- --> 节点,把 p 推到第 2、第 3 位。
常见失效场景:
-
<ul>\n<li>A</li></ul>中,li:first-child失效——换行符成了第一个子节点 -
<div><!-- hint --><p>段落</p></div>中,p:first-child完全不匹配 - 服务端注入提示
<div class="tip">...或 JS 动态插入隐藏div,也会破坏“第一个”身份
:first-of-type 是怎么绕过这些干扰的
它不看位置,只扫描父元素所有子节点,把同标签名(如所有 p、所有 li)拎出来排个序,取第一个。文本节点、注释、其他标签全被跳过。
所以这些情况它都能稳稳命中:
立即学习“前端免费学习笔记(深入)”;
-
<section><svg></svg><h2>标题</h2></section>→h2:first-of-type有效,h2:first-child无效 -
<div><h3>intro</h3><p>first</p><p>second</p></div>→p:first-of-type选中第一个p - 新闻列表里穿插了
<div class="ad">,article:first-of-type仍能精准定位首个正文块
什么时候非用 :first-child 不可
只有当你真正依赖“结构顺序”,而非“类型顺序”时才需要它。例如:
- 强制让容器第一个子元素占满宽度,不管它是
h1、img还是div:> *:first-child { width: 100%; } - 重置首子元素的
margin-top,且你 100% 确保它永远是你要重置的那类标签 - 配合
:not()做精细过滤,比如li:first-child:not(.disabled)
但要注意::first-child 和 :first-of-type 在纯同标签、无干扰的极简结构里结果一致,可一旦 HTML 变动(加注释、换行、插 slot),一致性立刻崩塌。
容易被忽略的关键细节
两者都只作用于**直接子元素**(除非你写后代选择器如 article p:first-of-type);:first-of-type 的“类型”仅指 HTML 标签名,和 class、id、data- 属性完全无关;display: none 的元素仍参与计数;IE9+ 全支持,无需降级。
真正复杂的地方在于嵌套层级——比如 ul li ul li:first-of-type,它匹配的是内层 ul 中第一个 li,而不是外层 ul 的某个子项。写之前,务必在 Elements 面板里展开父节点,逐个看清真实子节点序列,别信缩进或注释里的“意图”。


















