2.3 过滤选择器:漏斗式筛查 过滤选择器的定义可以用一句话立起来:它不从零开始找人,只在上一刀切出的集合里继续筛人。前两节的刀法都能独立命中,本节的刀具则必须挂在已有选择器后面—— 里真正的海选是 , 只是在海选结果里留下头一名。理解这层"二次加工"的定位,比背每一个过滤器的名字重要得多。 本节摘要:过滤选择器分三族——位置族(first、last、eq、lt、gt、even、odd)按序号留人,内容族(contains、has、empty、parent)按文本与子孙留人,可见性族(visible、hidden)按渲染状态留人;另有 not 与 filter 兼任万能漏斗。
过滤选择器的定义可以用一句话立起来:它不从零开始找人,只在上一刀切出的集合里继续筛人。前两节的刀法都能独立命中,本节的刀具则必须挂在已有选择器后面——$('li:first') 里真正的海选是 li,:first 只是在海选结果里留下头一名。理解这层"二次加工"的定位,比背每一个过滤器的名字重要得多。
本节摘要:过滤选择器分三族——位置族(first、last、eq、lt、gt、even、odd)按序号留人,内容族(contains、has、empty、parent)按文本与子孙留人,可见性族(visible、hidden)按渲染状态留人;另有 not 与 filter 兼任万能漏斗。

对着标本页把位置族跑一遍,重点体会"从零起算":
$('li').length; // 4 $('li').eq(1).text(); // 李四:eq 按集合内序号取人,从零开始 $('li:first').text(); // 张三 $('li:lt(2)').length; // 2:序号 0 和 1,张三与李四 $('li:even').length; // 2:偶数位是第 0、2 位,张三与王五
:even 与 :odd 的"偶奇"指的是序号而非第几个——人类直觉里的"第一个"在序号上是零,这套反直觉从 C 语言一路传到了这里。
// 找到文本里含"心悸"的条目——文本匹配是全文包含关系 $('.item:contains("心悸")').addClass('flag'); // 有急症标记类?不,:has 看的是"有没有匹配某选择器的后代" $('li:has(b)').length; // 含加粗子元素的条目数 // 空面板检测:排查渲染是否落空 $('#queue:empty').length; // 队列被清空时返回 1
:contains 的匹配是区分大小写的全文包含,参数加不加引号在 jQuery 3 均可,但老版本只认带引号的写法——老项目里见到 :contains("x") 别顺手把引号删了。.filter() 是内容族的万能替身,任何写不成选择器的条件都能写成函数:
$('.item').filter(function () { return $(this).data('vital') === 'mid'; // 生命体征为中等的患者 }).addClass('watch');
:visible 与 :hidden 的判定依据容易想当然。jQuery 的规则是:元素只要占据布局空间就算可见——visibility: hidden 或完全透明仍算可见,而 display: none、宽高为零、或本身不在文档里才算隐藏:
$('.item:hidden').length; // 页面初始全可见,返回 0 $('#queue').hide(); $('.item:hidden').length; // 4:父元素藏起来,子元素全算隐藏
这族过滤器在"折叠面板只处理可见行"这类场景里出场率极高,但它每次判定都要读布局信息,在长列表上反复调用会拖慢页面——第 7 章的性能账本里会再算它一笔。
位置与状态类过滤里,CSS 标准早已收编了一部分:first-child、last-child、nth-child(n)、checked、disabled 在原生查询里全部合法。jQuery 方言(eq、lt、gt、contains、visible)则出不了 jQuery 的大门。对照着记:
| 需求 | jQuery 写法 | 原生写法 |
|---|---|---|
| 第三个条目 | $('li').eq(2) |
document.querySelectorAll('li')[2] |
| 首个子元素 | $('li:first') |
querySelectorAll('li > *:first-child') 或取集合首项 |
| 文本包含 | $('.item:contains("心悸")') |
无对应,需取集合后 filter 判断 textContent |
| 已勾选 | $('input:checked') |
querySelectorAll('input:checked'),两边通用 |
规律与上一节一致:通用语法放心混用,方言一律留在 jQuery 世界里。拿不准时最稳的办法是先用基础选择器拿到集合,再用 .filter() 收窄——语义清晰,两边都不踩线。
选择器形态的过滤器还有一批方法形态的兄弟,它们直接作用在 jQuery 集合上,与漏斗模型天然契合:
$('li').not('.done'); // 反向筛:排除已完成,等价 :not(.done) $('li').slice(1, 3); // 按序号切一段,含头不含尾 $('li').is('.urgent'); // 不筛人,只问是非:集合里有没有急症 $('#queue').children().add('#beds li'); // 两路结果并成一批
.is() 值得单说:它不返回集合、只返回布尔,是"这个集合里存在匹配项吗"的问诊句。委托处理器里判断"点中的是不是急症条目"、离开页面前判断"还有没有未保存项",都用它——比先 filter 再数 length 干脆,也比读 length 更符合语义。
背景:标本页要做"未完成患者里的前两条加急标"。一次会诊写了 $('li:lt(2):not(.done)'),结果页面有时正常、有时加急标落在已完成的患者头上。
操作排查:把两段漏斗拆开分别数——$('li:lt(2)') 恒定命中前两名,:not(.done) 在其后再筛。若前两名恰好已完成,最终结果为空;而当时页面渲染顺序在拖拽排序后是动态的,于是症状时隐时现。真正要的是先排除、再取前二:$('li:not(.done):lt(2)')。
结果与解读:交换次序后行为稳定。过滤选择器从左到右依次作用,每一节的漏斗都作用在左边剩下的集合上——"先收窄、再取位"的次序不可颠倒。这条规则在链条更长时更要紧:海选、排除、定位三段的最佳排序是把代价最大的海选放最左、最具体的条件放右侧收口。变式:若"未完成"要按数据字段判断而非类名,把 :not(.done) 换成 .filter() 函数即可,位置条件 :lt(2) 保持在链尾不动画——次序原则不因漏斗形态改变。
$('li:first') 的主语是 li。:even 指偶数序号位,不是"第偶数个"。:has 看子孙、:contains 看文本、:empty 看子节点,三者别混。visibility 隐藏不算隐藏,display 隐藏才算。.filter(fn),写不成选择器的条件都交给它。常规元素的刀法已经齐全,下一节专攻一类特殊组织——表单控件,它们的"状态"比"位置"更值得筛。