01. filter() 方法概览
filter() 是 JavaScript 数组的强大筛选工具,专门用于从原始数组中挑选出符合条件的元素并返回一个新数组。核心特性是不修改原始数据,而是以回调函数为依据生成一个新的结果集。
在实际开发中,通过条件筛选数据是很常见的任务,例如过滤出可渲染的列表项、筛选符合条件的数据源,或者实现简单的搜索功能。理解 返回新数组而非修改原数组 的行为,是正确使用 filter 的关键。

01.1 什么是 filter()
filter() 会遍历数组的每一项,把每一项传给回调函数。只有回调返回 布尔值 true 的元素才会进入结果数组,其他的会被舍弃。
需要注意的是,回调接收三个参数:当前元素、索引、原数组,这为实现复杂筛选条件提供了灵活性。
const nums = [1, 2, 3, 4, 5];
const evens = nums.filter(n => n % 2 === 0); // [2, 4]
01.2 与其他数组方法的对比
与 map 相比,filter 不改变每个元素本身的映射结果,而是对“是否保留”进行判断;与 forEach 相比,filter 会产生一个新的数组而不是在原位执行副作用。
实战要点:当你需要基于条件提取子集时,优先考虑 filter,它的语义更清晰、可读性更高。
02. 基本语法与参数
基本语法:array.filter((value, index, array) => boolean) 返回一个新数组,包含所有通过测试的元素。
返回值类型:返回的始终是一个新数组,即使没有元素通过筛选,也会返回一个空数组。
02.1 回调函数的签名
回调函数接收三个参数,最常用的是第一个 当前元素 value,其次是 索引 index,最后是 原数组 array,这使得筛选条件可以根据上下文灵活构造。
示例要点:只要回调返回 true,元素就会被保留;如果返回 false 或其它假值,元素会被排除。
const arr = [10, 15, 20, 25];
const greaterThan15 = arr.filter((num) => num > 15); // [20, 25]
02.2 thisArg 的使用
thisArg 可以让回调在执行时绑定一个作用域对象,便于在条件中访问对象属性或方法。
如果你需要在回调中访问外部对象的状态,优先考虑传入 thisArg,代码可读性更好。
const threshold = { limit: 12 };
const list = [5, 12, 18, 3];
const filtered = list.filter(function(n) {return n > this.limit;
}, threshold); // [18]
03. 实战案例
案例驱动:通过具体场景展示 filter() 的实战用法,帮助你快速落地到真实业务中。
情景一:过滤数字数组中的偶数,可用于数据清洗或图表准备。
在这个例子中,关键点是使用简单的布尔测试来筛选出符合条件的数字。
const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = numbers.filter(n => n % 2 === 0); // [2, 4, 6]
案例要点:保持不可变性,原数组 numbers 不会被修改,确保数据流的可预测性。
情景二:根据对象属性过滤,常用于处理 API 返回的对象数组。
const users = [{ id: 1, role: 'admin' },{ id: 2, role: 'user' },{ id: 3, role: 'admin' }
];
const admins = users.filter(u => u.role === 'admin'); // [{id:1, role:'admin'}, {id:3, role:'admin'}]
案例要点:直接依据对象属性筛选,代码具有良好的可读性和扩展性。
情景三:去重(通过筛选键值唯一的项),常用于前端数据去重或分页前的预处理。
const items = [1, 2, 2, 3, 4, 4];
const unique = items.filter((v, i, self) => self.indexOf(v) === i); // [1,2,3,4]
案例要点:结合 indexOf/Sets,能够在保留顺序的前提下实现快速去重。
04. 性能与注意事项
性能特征:filter 的时间复杂度为 O(n) ,对大数组而言仍然高效,但要记住每次调用都会遍历整个数组。
如果你对性能敏感且数据量很大,可以考虑在筛选前进行分片、流式处理,或在前端分页加载时分步筛选,避免一次性处理过多数据。
// 对比示例:使用 filter 进行数据分流
const data = fetchData(); // 假设返回一个大数组
const visible = data.filter(item => item.visible);
与其他方法的对比:相比链式调用大量 filter 可能导致若干中间数组的创建,尽量合并条件,或在必要时用 复用策略、短路判断降低开销。
04.1 与 map/forEach 的对比
map 用于映射,forEach 用于副作用,filter 专注于筛选出新的子集。理解它们的职责差异,可以提升代码可读性和可维护性。
在实际场景中,往往需要进行多步数据处理,此时可以把 逻辑拆分成若干独立步骤,每一步只负责一种职责,减少耦合。
04.2 长数组的性能注意
对于极长的数组,避免在回调中执行复杂、耗时的操作,尽量把条件判断保持简单,必要时把复杂逻辑抽成外部函数。
分层筛选策略:先用一个简单条件筛出大致子集,再在结果子集上进行更精细的筛选,这样可以降低总体比较次数。
05. 常见坑与调试技巧
常见坑点:回调返回非布尔值时,结果会被强制转换为布尔值,导致非直观的结果。掌握这一点,能快速定位问题。
技巧要点:尽量让回调返回明确的布尔表达式,避免使用仅有副作用的代码块。
情景一:回调没有返回值,会导致整段逻辑返回空数组,因为 undefined 被强制为 false。
const arr = [1, 2, 3];
const bad = arr.filter(item => { item > 1; }); // 误用:不会返回 true,结果为空 []
const good = arr.filter(item => { return item > 1; }); // [2, 3]
调试要点:在回调中先写一个简单的条件,例如 return value > threshold,确保返回一个布尔值。
情景二:返回空数组的情况:当没有元素通过回调测试时,filter 会返回一个空数组,这与原数组长度无关。
const a = [0, -1, -2];
const positives = a.filter(x => x > 0); // []
调试建议:打印回调的返回值或中间变量,逐步确认条件是否正确并符合预期。


