:first-child和:last-child只匹配同级兄弟元素中第一个或最后一个元素节点,不包含文本、注释等非元素节点;应检查dom结构、使用:first-of-type/:last-of-type、子选择器>或data属性替代,并注意ie8兼容性及margin折叠问题。

first-child 和 last-child 选不到元素?检查父容器和兄弟节点
这两个伪类只对「同级兄弟元素中排第一/最后的那个」生效,不是「父容器里的第一个/最后一个子节点」。常见错误是父容器里混了文本节点、注释或 <script></script> 标签,导致目标元素实际不是 first 或 last。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 用浏览器开发者工具的「Elements」面板,右键目标元素 →「Scroll into view」,再看它的父节点下有哪些直接子节点(注意空格、换行也会生成 text node)
- 如果父容器内有
<!-- 注释 -->或未包裹的文本(比如<ul>文字<li>A</li> </ul>),:first-child就会匹配那个文本节点,而不是<li> - 想忽略非元素节点,改用
:first-of-type或:last-of-type—— 它们按标签类型计数,不care中间夹了啥
IE8 不支持 last-child?得用 JavaScript 回退
:first-child 在 IE7+ 都可用,但 :last-child 是 IE9+ 才支持。如果项目还要兼容 IE8,不能只靠 CSS。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 给列表加 class,比如
<ul class="nav"></ul>,然后用 JS 动态加 class:document.querySelector('.nav li:last-child').classList.add('last') - 更稳妥的做法是遍历
children(只返回元素节点,过滤掉 text/comment):const items = document.querySelector('.nav').children;<br>items[items.length - 1].classList.add('last'); - 避免用
childNodes,它包含所有节点类型,长度和索引容易误判
嵌套列表里 first-child 匹配错层?明确写死层级
比如 ul li:first-child a,本意是选最外层 <ul></ul> 下第一个 <li> 里的链接,但如果 <li> 里还有 <ul></ul>,里面的 <li> 也可能被匹配到——因为 :first-child 是相对其**直接父元素**而言的。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 用子选择器
>锁定层级:ul > li:first-child > a,这样就不会穿透到嵌套的<ul></ul>里 - 如果结构动态生成、层级不确定,优先用属性选择器或 data 属性代替,比如给首项加
data-first="true",CSS 写li[data-first] a - 别依赖视觉顺序:DOM 结构才是唯一依据,哪怕样式让某个
<li>看起来在最前,只要它不是 DOM 中第一个子元素,:first-child就不会命中
用 :first-child 做清除浮动?小心和 margin 折叠打架
有人用 li:first-child { margin-left: 0; } 来取消列表首项的左间距,这本身没问题;但若同时用了 display: inline-block 或浮动布局,又没处理父容器 BFC,margin 可能和父元素的 margin 合并,导致首项“消失”或位置偏移。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 确认父容器是否触发了 BFC:加
overflow: hidden或display: flow-root(后者现代浏览器支持良好) - 更干净的方案是用逻辑属性:
li:not(:first-child) { margin-inline-start: 1rem; },这样首项天然无 margin,也不用担心折叠 - 如果必须用
:first-child清 margin,记得连带检查vertical-align(inline-block 场景)或float方向是否一致,否则基线对齐可能出偏差
事情说清了就结束。真正麻烦的从来不是写对那行 CSS,而是 DOM 结构里多了一个看不见的换行符,或者某次重构悄悄把 ul 换成了 div。










