JavaScript Array.filter() 基础语法与使用示例
深入讲解 JavaScript Array.prototype.filter() 的基础语法、参数结构、常见使用示例以及与 map()、splice() 的关键差异,帮助开发者掌握数组条件筛选的最佳实践与避坑要点。
Array.prototype.filter() 是 JavaScript 中处理集合数据时最基础且高频使用的方法之一。它的主要职责是从现有数组中筛选出符合特定条件的子集:通过对原数组中的每一个元素依次运行一个测试断言函数,将所有返回真值(Truthy)的元素提取并归集到一个全新的浅拷贝数组中返回。在整个处理过程中,filter() 严格保持原数组不可变(Immutable),不会对源数据产生任何破坏性副作用。
一、filter() 的基础语法与参数规范
在 JavaScript 中调用 filter() 的标准语法格式如下:
const newArray = array.filter(callbackFn(element, index, array), thisArg);
该方法接收两个参数,其中断言回调函数是必需项,而执行上下文对象为可选项:
- callbackFn(断言回调函数):在数组每个元素上执行的测试函数。每次触发调用时,JavaScript 引擎会向该函数传递三个实参:
element:当前正在处理的数组项。index(可选):当前处理项在原数组中的索引位置(从0开始)。array(可选):调用了filter()方法的原始数组对象。
- thisArg(可选执行上下文):当执行
callbackFn时用作this绑定的值。注意:若使用 ES6 箭头函数编写回调,由于箭头函数自动词法绑定外层作用域,此参数将被忽略。 - 返回值:返回一个包含所有通过测试元素的全新数组。如果没有元素满足测试条件,则返回一个合法的空数组
[]。
filter() 返回的新数组是一个独立的数组实例,但它所执行的是浅拷贝(Shallow Copy)。如果数组中存放的是基本类型(如数字、字符串),新数组中拥有独立的数值;如果存放的是对象、数组等引用类型,新数组中存放的是相同的对象引用地址。
二、常用操作场景与代码示例
下面通过四个在日常业务与前端开发中最为常见的场景,展示 filter() 的具体用法与编写模式。
1. 基础类型过滤:提取符合数值区间的元素
对于纯数字数组,通常用于数值阈值过滤、奇偶数提取等基础计算:
// 示例:筛选出及格分数(>= 60)
const scores = [45, 78, 92, 59, 88, 30];
// 箭头函数直接返回关系表达式的求值结果(true 或 false)
const passingScores = scores.filter(score => score >= 60);
console.log(passingScores); // 示例输出: [78, 92, 88]
console.log(scores); // 示例输出: [45, 78, 92, 59, 88, 30](原数组完好无损)
2. 对象数组过滤:多维度属性匹配
在接口联调、表格查询等业务中,数组往往由对象构成。我们可以在断言函数中读取对象的具体字段并进行组合校验:
// 示例:从商品列表中筛选出“上架中”且“价格小于 100 元”的商品
const products = [
{ id: 101, title: '机械键盘', price: 299, inStock: true },
{ id: 102, title: '静音鼠标', price: 79, inStock: true },
{ id: 103, title: '鼠标垫', price: 25, inStock: false },
{ id: 104, title: '收纳支架', price: 68, inStock: true }
];
const affordableInStock = products.filter(product => {
return product.inStock && product.price < 100;
});
console.log(affordableInStock);
// 示例输出:
// [
// { id: 102, title: '静音鼠标', price: 79, inStock: true },
// { id: 104, title: '收纳支架', price: 68, inStock: true }
// ]
3. 数据清洗:剔除空值与假值
从外部数据源接收到的数组可能含有未定义值或无效数据。可以将内置的 Boolean 构造函数直接作为回调传入,快速滤掉 null、undefined、空字符串 "" 和 NaN:
// 示例:剔除所有假值(falsy items)
const mixedList = ['Apple', '', null, 'Banana', undefined, 0, false, 'Orange'];
const validItems = mixedList.filter(Boolean);
console.log(validItems);
// 示例输出: ['Apple', 'Banana', 'Orange']
// 注意:数字 0 和布尔值 false 也会被视作假值过滤掉
如果业务要求保留数字 0,只清除 null 与 undefined,应显式使用不等于判断:
// 示例:仅过滤 null 与 undefined,保留 0 和有效数值
const readings = [12.5, 0, null, 18.2, undefined, 0];
const cleanReadings = readings.filter(val => val !== null && val !== undefined);
console.log(cleanReadings); // 示例输出: [12.5, 0, 18.2, 0]
4. 利用 index 与 array 参数:实现数组快速去重
通过对比当前元素的遍历下标 index 与该元素在数组中首次出现的下标(array.indexOf(item)),可以完成一维基础数组的快速去重:
// 示例:提取唯一的标签列表
const tags = ['javascript', 'css', 'html', 'javascript', 'vue', 'css'];
const uniqueTags = tags.filter((item, index, arr) => {
// 只有当前项索引与首次出现的索引一致,才说明是第一次遇到该值
return arr.indexOf(item) === index;
});
console.log(uniqueTags); // 示例输出: ['javascript', 'css', 'html', 'vue']
三、关键机制对照:filter() 与相关方法的差异与影响
在数组处理中,选择正确的工具直接决定了代码的可维护性与执行安全性。下面将 filter() 与日常最易混淆的 map() 及 splice() 展开关键对比:
| 对比维度 | Array.prototype.filter() | Array.prototype.map() | Array.prototype.splice() |
|---|---|---|---|
| 设计意图 | 按条件抽取子集(剔除不需要的元素) | 对元素逐一转换映射(改变数据形态) | 在指定位置增删或替换现有元素 |
| 是否修改原数组 | 否(保持原数组纯净无损) | 否(保持原数组纯净无损) | 是(破坏性原地修改原数组) |
| 输出数组长度 | 0 至 原数组长度(长度可变) | 严格等于原数组长度 | 返回被删除元素数组,原数组长度缩水 |
| 回调函数返回值 | 布尔值(真值保留,假值丢弃) | 变换后的任意目标值 | 非回调模式(按索引传参) |
| 编程风格 | 声明式 / 纯函数 | 声明式 / 纯函数 | 命令式 / 有副作用 |
不可变性为什么在工程中至关重要?
filter() 遵循不可变(Immutable)原则,返回新实例而不修改源数组。这在现代前端框架(如 React 与 Vue)中具有重要价值:
- 避免副作用污染:若多个组件或模块共享同一份数据列表,原地修改(如
splice())会导致其他组件的数据在无感知的情况下发生变更,诱发极其隐蔽的状态异常。 - 适配引用比较机制:React 的
useState、useMemo等依赖浅层引用对比(prev !== next)来确定是否触发重新渲染。使用filter()产出新数组,能保证状态更新被框架稳定捕获。
四、常见避坑要点与注意事项
为了编写出健壮的代码,开发者在使用 filter() 时必须规避以下四个高频陷阱:
1. 切勿在 filter 中使用异步回调(async function)
filter() 内部是完全同步执行的,它不会等待 async 函数返回的 Promise 完成。在 JavaScript 的布尔上下文中,任何 Promise 实例对象都会被评估为真值(Truthy)。这意味着如果传入 async 函数,所有项都会被当成 true 保留,条件过滤彻底失效:
// 错误示范:async 函数作为回调
const userIds = [1, 2, 3, 4];
const result = userIds.filter(async (id) => {
return id % 2 === 0; // 返回的是 Promise<boolean>,在条件判断中恒为 true!
});
console.log(result); // 错误输出: [1, 2, 3, 4],未能过滤任何元素!
// 正确方案:使用 Promise.all 并发求值后进行同步过滤
async function filterAsync(arr, predicate) {
const flags = await Promise.all(arr.map(predicate));
return arr.filter((_item, index) => flags[index]);
}
2. 浅拷贝对对象引用的影响
当数组元素是对象时,filter() 产出的新数组中存放的是对原有对象的相同内存引用。如果后续代码修改了新数组中某个对象的内部属性,原数组中对应的对象也会同步被修改:
const originalList = [{ id: 1, name: 'Alice' }];
const filteredList = originalList.filter(item => item.id === 1);
// 误区操作:直接修改新数组中的对象字段
filteredList[0].name = 'Bob';
// 影响:原数组中的同名对象也被篡改
console.log(originalList[0].name); // 输出: 'Bob'
如果业务后续需要独立篡改这些对象属性,应在过滤的同时或之后进行对象克隆(例如配合 { ...item } 或结构化克隆)。
3. 稀疏数组(Sparse Array)会跳过空槽位
如果数组中存在未被分配内存的空槽位(Hole,例如使用 new Array(5) 创建或者通过 delete arr[0] 留下的空位),filter() 会直接跳过它们,不会针对空槽调用回调函数,也不会将它们放入新数组。
const sparseList = [1, , 3]; // 索引 1 处为未初始化的空槽
const compressedList = sparseList.filter(() => true);
console.log(compressedList); // 示例输出: [1, 3]
console.log(compressedList.length); // 示例输出: 2(空槽已被剔除)
五、总结与实践建议
JavaScript 的 Array.prototype.filter() 以其直观的语义和纯函数特性,成为构建清晰数据流的基础工具。在实际工程落地时,建议遵循以下三项实践原则:
- 保持断言函数纯净:断言函数只负责测试当前项并返回布尔结果,避免在回调内部修改外部变量或操作原数组。
- 明确真值边界:特别注意
0、""等假值在数据清洗时的影响,按需使用显式比较或Boolean构造函数。 - 灵活组合链式调用:将
filter()与map()、reduce()等无缝串联,可以优雅地替代繁琐的命令式for循环,大幅提升代码的可读性与健壮性。
JS语法错误会阻止加载吗?区分SyntaxError与资源加载失败
2026-11-05 16:08:46
JavaScript常见SyntaxError语法错误排查与修复方法
2026-11-05 13:58:44
JS中slice、substring和substr截取字符串的区别
2026-11-05 13:43:47
JS 转 TS 手动迁移最佳实践:从配置 tsconfig 到消除 any 类型
2026-11-05 11:53:32
为什么JS数组push后得到的是数字而不是数组?
2026-11-05 09:59:18








![JS创建二维数组:为什么Array(n).fill([])会导致数据联动](https://img.javascript.cn/tit/articles/13236/20261102/f618c991891ff37efb12df6af2ce0ccb0da8779a907fe1d2f7275d60c84fdd77.webp)