3.1 DOM 遍历:沿树行走的清扫路线 接手一个别人写了一半的页面,最常见的工作状态是:手里只有一个事件发生处的元素,目标却在树的另一头。选择器是"从全文档找人",遍历则是"从手头这个人出发,向下、向旁、向上走"。本节把三条行走路线的方法族一次走全——它是本章其余三节的取货通道,属性、样式、结构修改要操作谁,全靠这里先走到。 本节摘要:遍历方法按方向分三路:向下有 (直接子代)与 (全部后代中按选择器再筛),向旁有 、 、 及其 All 系列,向上有 、 、 。每次行走都返回新的 jQuery 集合, 方法可以在链上退回上一步的集合。 向下走:children 与 find 的分野 规则与 2.2 节的空格、大于号完全同构: 对应大于号(只要儿子), 对应空格(子孙都要)。
接手一个别人写了一半的页面,最常见的工作状态是:手里只有一个事件发生处的元素,目标却在树的另一头。选择器是"从全文档找人",遍历则是"从手头这个人出发,向下、向旁、向上走"。本节把三条行走路线的方法族一次走全——它是本章其余三节的取货通道,属性、样式、结构修改要操作谁,全靠这里先走到。
本节摘要:遍历方法按方向分三路:向下有
children(直接子代)与find(全部后代中按选择器再筛),向旁有siblings、next、prev及其 All 系列,向上有parent、parents、closest。每次行走都返回新的 jQuery 集合,end方法可以在链上退回上一步的集合。
$('#ward').children(); // 全部直接子元素:h2 与 ul $('#ward').children('ul'); // 直接子元素里再留 ul $('#ward').find('li'); // 后代里所有 li,嵌套多深都算 $('#ward').find('.urgent'); // 后代里带 urgent 类的元素
规则与 2.2 节的空格、大于号完全同构:children 对应大于号(只要儿子),find 对应空格(子孙都要)。一个实用差别是 find 必须带参数,而 children 可以不带——"看看它有几个孩子"这种探查动作,children() 后看 length 即可。
$('.urgent').siblings(); // 同父的全部兄弟,不含自己 $('.urgent').next(); // 紧邻的下一个 $('.urgent').prev(); // 紧邻的上一个 $('.urgent').nextAll(); // 身后所有兄弟 $('.urgent').prevUntil('.done') // 上一个直到 .done 之前(不含两端)
这与层级选择器的加号、波浪号是同一件事的方法形态。什么时候用选择器、什么时候用方法?经验法则是:关系路径能用一句选择器说清就用选择器,需要"先拿到一个动态元素再找邻居"(比如事件回调里的 this)就必须用方法——第 4 章的事件处理里你会不断见到后者。
向上走的三个方法各有分工,混用是高频错误源:
$('.urgent').parent(); // 直接父元素:只有 ul 一个 $('.urgent').parents(); // 全部祖先:ul、main、body、html 依次入列 $('.urgent').closest('.panel'); // 从自己出发向上找,第一个带 panel 类的祖先
closest 的两个细节值得划线。其一,查找范围包含元素自己——$(this).closest('li') 在回调里几乎是条件反射式的写法:不管点中的是条目里的哪个子元素,都能退回承载事件的那个条目。其二,结果最多一个,parents 带选择器则可能拿到多个。第 4 章的事件委托会把 closest 用成标配。

遍历方法的返回值是新集合,原集合被压进栈里。end 就是把栈顶弹出来的那一步:
$('#queue') .find('.urgent') // 集合变为:急症条目 .addClass('flag') .end() // 退回:整个 queue 的原集合 .find('.stable') // 再从原集合出发找稳定患者 .addClass('watch');
不用 end 的等价写法是把两段拆开存变量,可读性见仁见智;但读别人的代码必须认识 end,否则链到一半"集合悄悄换人"会看得一头雾水。同理,filter 与 not 也是"返回新集合"的成员,链式语义一致。
| jQuery | 原生 | 备注 |
|---|---|---|
$el.children('ul') |
el.children 后自行 filter |
原生 children 不收参数 |
$el.find('.urgent') |
el.querySelectorAll('.urgent') |
语义一致 |
$el.next() |
el.nextElementSibling |
原生没有对应 next 的容错封装 |
$el.closest('.panel') |
el.closest('.panel') |
原生同名同义,现代浏览器可直接用 |
$el.siblings() |
无直接对应 | 常用 [...el.parentNode.children] 减去自己 |
closest 已经是 DOM 标准,这是 jQuery 反哺原生语言最出名的例子之一——当年它在类库里发明,如今成了每个浏览器自带的方法。
两批结果要合成一批时用 add;想在换集合之后"把旧集合也带上"用 addBack:
$('#queue .urgent').add('#beds .urgent'); // 两个区域的急症并成一批处理 $('#queue').find('.urgent').addBack(); // 急症条目 + queue 本身,一起加描边
addBack 的典型岗位是"容器与命中项一起高亮":find 换集合后把出发站也拉回来,一条链写完,不用回头再查一次容器。
背景:标本页的就诊卡片嵌在面板里,需求是"点击卡片任意位置时给整张面板加描边"。一次会诊顺手写了 $(this).parents('.panel'),功能正常;两周后样式升级给卡片外又包了一层带 panel 类的内衬,页面一刷新,描边加在了内衬上。
排查与修法:parents 返回全部匹配祖先,操作类方法作用在每一层上——层级一变,多出来的那层也被波及。这里要的是"最近的、恰好一层",正解是 $(this).closest('.panel'):不管中间将来再插多少层装饰容器,命中的永远是离自己最近的那个 panel。解读:三个向上方法的选择其实是一道"要几层"的单选题——parent 恰好一层且不筛类型、closest 最近的一层且按选择器筛、parents 来者不拒全栈收下。选错不报错,只在结构变化那天发作。变式:若真需要"从卡片往上到面板之间的每一层都改",才轮到 parentsUntil('.panel'),两端不包含,区间语义正好。
children 只要儿子、find 子孙全要;向旁 next、prev 走一步、siblings 全体同辈;向上 parent 一步、parents 全族、closest 就近含己。closest 包含自身且至多命中一个,是委托与冒泡场景的标准动作。end 退回上一站;链式调用前后集合是否"换人"要随时有数。closest、children、nextElementSibling 覆盖了大半高频需求,迁移前先对照表格。人已经走到病灶跟前。下一节翻开元素的档案:特性和属性这两本账,为什么记的常常不是同一个数。