map()和filter()配合的实用技巧
一、两层嵌套数组的过滤
1.1 ❌ 臃肿方式:双重循环与临时变量
// 原始数据:二维用户数组
const users2Level = [
[{name: 'John', age: 25}, {name: 'Jane', age: 30}],
[{name: 'Bob', age: 35}, {name: 'Alice', age: 20}]
];
// 过滤出年龄大于30的用户
function filter2LevelArrayBulky(array, condition) {
let result = [];
for (let i = 0; i < array.length; i++) {
const innerArray = array[i];
let innerResult = [];
for (let j = 0; j < innerArray.length; j++) {
if (condition(innerArray[j])) {
innerResult.push(innerArray[j]);
}
}
if (innerResult.length > 0) {
result.push(innerResult);
}
}
return result;
}
const filteredBulky = filter2LevelArrayBulky(users2Level, user => user.age > 30);
console.log(filteredBulky);
// 输出: [[{name: 'Bob', age: 35}]]
缺点分析:
- 代码冗长,需要手动管理循环和索引
- 需要创建多个临时变量(
innerArray
,innerResult
) - 可读性差,容易出错
1.2 ✅ 简洁方式:结合 map
和 filter
const users2Level = [
[{name: 'John', age: 25}, {name: 'Jane', age: 30}],
[{name: 'Bob', age: 35}, {name: 'Alice', age: 20}]
];
const filteredConcise = users2Level.map(innerArray =>
innerArray.filter(user => user.age > 30)
).filter(innerArray => innerArray.length > 0);
console.log(filteredConcise);
// 输出: [[{name: 'Bob', age: 35}]]
优点分析:
- 代码简洁易读
- 使用高阶函数,避免手动循环
- 链式调用,逻辑清晰
1.3 ✅ 更优方式:单次 reduce
完成
const filteredOptimal = users2Level.reduce((acc, innerArray) => {
const filteredInner = innerArray.filter(user => user.age > 30);
if (filteredInner.length > 0) {
acc.push(filteredInner);
}
return acc;
}, []);
console.log(filteredOptimal);
// 输出: [[{name: 'Bob', age: 35}]]
优点分析:
- 只需一次遍历外部数组
- 避免创建中间数组
二、多层嵌套数组的过滤
当遇到三层或更多层嵌套时,问题变得更加复杂。
2.1 ❌ 臃肿方式:深度嵌套循环
// 三层嵌套数组
const users3Level = [
[
[{name: 'John', age: 25}, {name: 'Jane', age: 30}]
],
[
[{name: 'Bob', age: 35}, {name: 'Alice', age: 20}],
[{name: 'Tom', age: 40}, {name: 'Jerry', age: 28}]
]
];
function filter3LevelArrayBulky(array, condition) {
let result = [];
for (let i = 0; i < array.length; i++) {
const middleArray = array[i];
let middleResult = [];
for (let j = 0; j < middleArray.length; j++) {
const innerArray = middleArray[j];
let innerResult = [];
for (let k = 0; k < innerArray.length; k++) {
if (condition(innerArray[k])) {
innerResult.push(innerArray[k]);
}
}
if (innerResult.length > 0) {
middleResult.push(innerResult);
}
}
if (middleResult.length > 0) {
result.push(middleResult);
}
}
return result;
}
const filtered3LevelBulky = filter3LevelArrayBulky(users3Level, user => user.age > 30);
console.log(filtered3LevelBulky);
// 输出: [[[[{name: 'Bob', age: 35}]], [[{name: 'Tom', age: 40}]]]]
缺点分析:
- 代码极度臃肿,难以维护
- 循环嵌套深度随数组维度增加
- 容易引入边界错误
2.2 ✅ 简洁方式:递归处理
function filterNestedArray(array, predicate) {
return array.reduce((acc, item) => {
// 如果是数组,递归处理
if (Array.isArray(item)) {
const filteredSubarray = filterNestedArray(item, predicate);
if (filteredSubarray.length > 0) {
acc.push(filteredSubarray);
}
}
// 如果是对象且符合条件,直接保留
else if (predicate(item)) {
acc.push(item);
}
return acc;
}, []);
}
const filtered3LevelRecursive = filterNestedArray(users3Level, user => user.age > 30);
console.log(filtered3LevelRecursive);
// 输出: [[[[{name: 'Bob', age: 35}]], [[{name: 'Tom', age: 40}]]]]
优点分析:
- 代码简洁,适用于任意深度嵌套
- 逻辑清晰,易于维护
- 可处理混合型嵌套结构(数组和对象混合)
2.3 ✅ 替代方案:使用 flat
方法先扁平化再过滤
// 先完全扁平化,再过滤,最后尝试恢复结构(适用某些场景)
const fullyFlattened = users3Level.flat(Infinity);
const filteredFlattened = fullyFlattened.filter(user => user.age > 30);
console.log(filteredFlattened);
// 输出: [{name: 'Bob', age: 35}, {name: 'Tom', age: 40}]
适用场景:
- 不需要保持原始嵌套结构时
- 只需获取所有符合条件的叶子节点
局限性:
- 丢失了原始嵌套结构信息
- 无法区分元素来自哪一层级
三、处理复杂对象结构
当数组中包含复杂对象时,可能需要根据对象属性进行过滤。
3.1 ✅ 简洁方式:递归处理对象数组
function filterNestedObjectArray(array, predicate) {
return array.reduce((acc, item) => {
if (Array.isArray(item)) {
// 如果是数组,递归处理
const filteredArray = filterNestedObjectArray(item, predicate);
if (filteredArray.length > 0) {
acc.push(filteredArray);
}
} else if (item !== null && typeof item === 'object') {
// 如果是对象,检查是否满足条件
if (predicate(item)) {
acc.push(item);
}
// 递归处理对象属性值
Object.values(item).forEach(value => {
if (Array.isArray(value)) {
const filteredValue = filterNestedObjectArray(value, predicate);
if (filteredValue.length > 0) {
acc.push(filteredValue);
}
}
});
}
return acc;
}, []);
}
// 复杂嵌套结构示例
const complexNestedArray = [
{name: 'John', age: 25, tags: ['active', 'new']},
[
{name: 'Jane', age: 30, tags: ['active', 'vip']},
{name: 'Bob', age: 35, tags: ['inactive', 'vip']}
],
{name: 'Alice', age: 20, tags: ['new']}
];
const filteredComplex = filterNestedObjectArray(complexNestedArray, item =>
item.age > 25 && item.tags && item.tags.includes('vip')
);
console.log(filteredComplex);
// 输出: [{name: 'Jane', age: 30, tags: ['active', 'vip']}, {name: 'Bob', age: 35, tags: ['inactive', 'vip']}]
四、性能优化建议
处理大型多层嵌套数组时,性能很重要:
- 避免不必要的递归:确保递归有明确的终止条件,防止无限递归。
- 使用迭代替代深度递归:对于特别深的嵌套结构,考虑使用迭代方式避免栈溢出。
- 利用现代JS引擎优化:现代JavaScript引擎对内置方法如
map
、filter
、reduce
进行了优化。 - 考虑使用专用库:对于特别复杂的操作,考虑使用 Lodash 等库的
_.flattenDeep
和_.filter
方法。
五、总结对比
方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
多重循环 | 简单固定层次结构 | 直观易懂 | 代码臃肿,难以维护 |
map +filter 链式调用 | 简单到中等复杂度 | 代码简洁,可读性好 | 创建中间数组,性能一般 |
递归处理 | 任意深度嵌套结构 | 灵活性强,代码简洁 | 深度过大可能栈溢出 |
先扁平化再过滤 | 不需要保持原始结构 | 非常简单直接 | 丢失嵌套结构信息 |
选择哪种方式取决于你的具体需求:
- 对于简单固定层次的数组,使用
map
和filter
组合。 - 对于深度嵌套且层次不确定的数组,使用递归方法。
- 当你只关心数据本身而不需要保持原始结构时,可以先扁平化再过滤。
提示:在实际项目中,对于特别复杂的数据过滤需求,可以考虑使用函数式编程库如 Lodash,它提供了
_.filter
、_.flattenDeep
等强大工具,能简化你的代码。