在jQuery的日常使用中,filter()和find()是两个高频方法,但它们的语义完全不同:前者是"在当前集合里做减法",后者是"到后代里做加法"。混用这两个方法,轻则选不到元素导致逻辑失效,重则在遍历大量DOM节点时造成明显的性能问题。本文将从底层机制、性能表现和实际场景三个角度,把这两个方法彻底讲清楚。

一、两个方法的本质区别:作用对象不同
要理解filter()和find()的差异,首先要明确它们操作的对象。filter()接收一个选择器表达式或函数,然后在当前jQuery对象已包含的元素集合内部进行过滤,结果集是原集合的子集,集合只会变小不会变大。而find()会以当前集合中的每个元素为起点,向下遍历所有后代节点,返回匹配的后代元素组成的新集合,与原集合几乎没有交集。
用一个简单的结构来说明:假设页面里有一组<ul class="list">,每个<li>下还嵌套了<span>。执行$('li').filter('.active')得到的是带有active类的li本身;而执行$('li').find('span')得到的是li内部的span,li自己不会出现在结果中。前者是"挑人",后者是"找人的孩子"。
还有一个容易忽略的细节:filter()的参数必须能匹配元素本身,而find()的参数只匹配后代。如果把filter('span')用在li集合上,结果永远是空集,因为li本身不可能是span。这种语义错误在代码评审中相当常见,且不会报错,只会静默返回空的jQuery对象,后续链式调用全部失效,排查起来比较费劲。
二、链式调用中的性能差异分析
从源码角度看,两个方法的实现路径差异很大。filter()底层依赖winnow()函数,核心是对现有集合做一次线性扫描,用matchesSelector判断每个元素是否匹配,时间复杂度接近O(n),n是当前集合的长度,且完全不触碰DOM树的其他部分,开销很稳定。
find()则复杂得多。jQuery 1.6之后它基于querySelectorAll实现,会先判断浏览器是否支持跨上下文查询,再对起点集合中的每个元素执行后代查询。虽然现代浏览器下这个成本已经不高,但当起点集合很大、DOM层级很深时,每个起点都会产生一次独立的查询操作,累计的耗时就不能忽视了。
可以通过一段测试代码直观感受差异:
// 构造测试环境:5000个li,每个li下有3个span
var html = '';
for (var i = 0; i < 5000; i++) {
html += '<li class="item" data-i="' + i + '">' +
'<span class="a">a</span><span class="b">b</span>' +
'</li>';
}
$('#testList').append(html);
console.time('filter');
$('li').filter('[data-i=2500]');
console.timeEnd('filter');
console.time('find');
$('li').find('span.b');
console.timeEnd('find');在Chrome下的典型结果是filter耗时在1毫秒左右,而find耗时数倍于它,因为find要遍历15000个span并逐一判断匹配。这个数字会随DOM规模线性增长。所以在链式调用中,一个重要的优化原则是:先用filter把集合缩小,再执行find,而不是反过来。写法上$(el).filter('.active').find('span')通常优于先find再筛选,因为后者的查询基数大得多。
另外要注意,选择器本身写法也影响性能。如果一段逻辑既能用filter()在已有集合上完成,也能用一次新的选择器查询完成(例如直接写$('.list .active')),往往一次性查询更快,因为现代浏览器的querySelectorAll对组合选择器做了高度优化,比jQuery的链式过滤少了一层中间集合的构建成本。
三、典型使用场景与最佳实践
场景一:列表高亮与状态筛选。当需要对一批已有元素按状态二次筛选时,filter()是正确选择。比如表格中只对被勾选的行做样式处理:
// 只给当前选中的行加高亮类
$('table tr').filter(':even').addClass('stripe');
// 配合函数形式的filter,可以做复杂判断
$('table tr').filter(function (index) {
return $(this).find(':checked').length > 0;
}).addClass('selected');场景二:子元素查找与事件委托。在容器内部定位具体控件时用find(),它比重复执行全局选择器更精准,也天然限定了作用范围,避免误伤页面其他区域的同名元素。
// 只在当前面板内查找按钮,不影响其他面板
var $panel = $('#settingsPanel');
$panel.find('.btn-save').on('click', function () {
$panel.find('input').prop('disabled', true);
});场景三:与end()配合回退。链式调用中find()和filter()都会推入jQuery的内部栈,配合end()可以退回上一步的结果集,这在写紧凑的链式代码时非常实用:
// 给active项加类,然后回到li集合统一设置属性
$('li')
.filter('.active').addClass('highlight').end()
.attr('role', 'listitem');总结一下判断标准:目标元素在当前集合里,用filter();目标元素在当前集合的后代里,用find()。写链式调用时遵循"先缩小范围、再深入查找"的顺序,能显著减少不必要的DOM遍历。在性能敏感的大列表场景中,还建议尽量用一次原生组合选择器替代多层链式过滤,并用缓存变量保存中间结果,避免重复执行相同的查询,这些细节叠加起来,才是真正高效的jQuery代码写法。
jQuery filterjQuery find链式调用修改时间:2026-09-09 08:02:34