在处理列表数据时,我们经常需要根据用户输入的关键字从一堆对象里挑出符合条件的记录,比如电商网站的商品搜索、后台管理系统的用户过滤。传统写法往往是一层嵌套一层的for循环加if判断,代码既长又难读。ES6之后,JavaScript为数组提供了filter、find、some等一系列高阶方法,配合字符串的includes和match,可以把这类筛选逻辑压缩到几行代码里,而且表达能力更强。本文就来详细拆解这些用法。

一、基础用法:filter加includes实现子串匹配
假设有一份用户数据,每条记录包含姓名、邮箱和城市。当用户在搜索框里输入一个关键字时,我们需要找出姓名中包含该关键字的所有记录。最直接的方案就是用Array.prototype.filter配合String.prototype.includes,前者负责过滤数组,后者负责判断子串关系。
const users = [
{ id: 1, name: '张伟', email: 'zhangwei@ipipp.com', city: '北京' },
{ id: 2, name: '李娜', email: 'lina@ipipp.com', city: '上海' },
{ id: 3, name: '王小明', email: 'xiaoming@ipipp.com', city: '广州' }
];
function searchUsers(list, keyword) {
return list.filter(user => user.name.includes(keyword));
}
console.log(searchUsers(users, '小')); // 输出王小明那条记录</code>这段代码的核心在于filter的回调函数。filter会遍历数组中每一个元素,把回调返回值为true的元素收集到一个新数组里,原数组保持不变。这种不可变的风格在React、Vue等框架里尤其受欢迎,因为它不会偷偷修改状态,便于做变更检测。
需要注意的是,includes是区分大小写的。如果搜索英文名时希望大小写不敏感,可以先把两边都转成小写再比较,例如user.name.toLowerCase().includes(keyword.toLowerCase())。另外,如果字段可能为null或undefined,调用includes会直接抛错,建议先用String(user.name || '')做一层兜底转换,保证程序的健壮性。
二、进阶匹配:startsWith、endsWith与正则的配合
除了模糊包含,实际业务里还常常有前缀匹配和后缀匹配的需求。比如输入框用于联想提示时,通常只需要匹配开头几个字,这时startsWith比includes语义更明确,性能上在大数据量时也更占优势,因为匹配到前缀就可以提前结束判断。
// 只匹配以关键字开头的姓名,并且限制返回条数
function suggestUsers(list, prefix, limit = 5) {
const lowerPrefix = prefix.toLowerCase();
return list
.filter(user => user.name.toLowerCase().startsWith(lowerPrefix))
.slice(0, limit);
}当匹配规则更复杂时,比如要求关键字出现在多个字段中任意一个,或者需要支持多关键字按空格分隔查询,正则表达式配合match方法会更灵活。下面的例子把用户输入按空格拆成多个关键字,要求记录同时包含所有关键字(字段不限),这是搜索框里非常经典的模糊搜索逻辑。
function multiKeywordSearch(list, input) {
const keywords = input.trim().split(/\s+/).filter(Boolean);
if (keywords.length === 0) return list;
return list.filter(user => {
// 把所有字段拼成一个待匹配文本
const haystack = `${user.name} ${user.email} ${user.city}`.toLowerCase();
return keywords.every(kw => haystack.includes(kw.toLowerCase()));
});
}这里用到了every方法,它要求数组中每一项都满足条件才返回true,正好对应逻辑与的关系。反过来,如果希望任意一个关键字命中即可,把every换成some就行,对应逻辑或。理解这几个方法的语义差异,可以组合出各种复杂的筛选条件,而不需要写任何显式循环。
如果确实需要正则的强大能力,比如匹配邮箱域名部分,可以构造RegExp对象来匹配,但要注意用户输入中的特殊字符需要转义,否则输入一个点号或星号就可能被当作正则元字符,轻则结果不对,重则触发正则灾难性回溯导致页面卡死。稳妥的做法是写一个escape函数,把特殊字符前面加上反斜杠。
三、性能优化与细节陷阱
数据量小的时候,上面这些写法性能差异可以忽略。但当你面对上万条记录、且用户每敲一个字符就触发一次搜索时,就需要考虑优化手段了。第一个思路是减少重复计算:把toLowerCase的结果缓存起来,避免每次筛选都对全部记录做一遍字符串转换。可以在数据加载时预先构建一份带小写索引字段的数组。
// 预处理:给每条记录挂一个搜索索引字段
const indexed = users.map(user => ({
...user,
_searchIndex: `${user.name} ${user.email} ${user.city}`.toLowerCase()
}));
// 搜索时直接使用缓存字段,省去每次转换的开销
function fastSearch(list, keyword) {
const kw = keyword.toLowerCase();
return list.filter(item => item._searchIndex.includes(kw));
}第二个思路是节流控制。用户输入时连续触发搜索会浪费大量计算,用节流函数把搜索频率限制在每两三百毫秒一次即可,体验基本不受影响。此外,找到第一条匹配记录就停止的场景应该用find而不是filter,判断是否存在匹配用some,它们都在命中后提前终止遍历,而filter一定会跑完全部元素。
还有一个容易被忽视的坑是中文拼音搜索。如果产品要求输入拼音也能匹配中文姓名,纯字符串方法就不够了,需要引入拼音库先把姓名转成拼音建索引,或者在后端完成这项工作。前端筛适合数据量在几千条以内的场景,数据量再大就应该把搜索请求交给服务端接口,通过URL参数传递关键字,前端只负责展示结果,这也是架构上更合理的选择。
总结一下,ES6的数组方法加字符串方法组合,已经能覆盖日常绝大多数对象数组筛选需求。关键是根据业务场景选择合适的匹配语义,匹配开头用startsWith,模糊包含用includes,多关键字组合用every或some,同时注意大小写处理、空值兜底和大数据量下的预处理优化,代码量和可维护性都会比传统循环写法好上一大截。
JavaScript数组筛选ES6子字符串匹配修改时间:2026-09-10 06:02:37