数组高阶方法
00:00
JavaScript数组高阶方法详解:reduce、flatMap及函数式数组操作模式。
1. reduce — 万能归约
1.1 基本语法
array.reduce((accumulator, currentValue, index, array) => {
return newAccumulator;
}, initialValue);
1.2 常见用法
求和/求积:
const sum = [1, 2, 3, 4].reduce((acc, cur) => acc + cur, 0); // 10
const product = [1, 2, 3, 4].reduce((acc, cur) => acc * cur, 1); // 24
对象分组:
const people = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 25 },
];
const byAge = people.reduce((acc, person) => {
const key = person.age;
(acc[key] ??= []).push(person);
return acc;
}, {});
// { 25: [{ name: 'Alice', age: 25 }, { name: 'Charlie', age: 25 }],
// 30: [{ name: 'Bob', age: 30 }] }
// 使用 Object.groupBy(ES2024)
const byAge2 = Object.groupBy(people, (p) => p.age);
数组扁平化:
const nested = [[1, 2], [3, [4, 5]], [6]];
const flat = nested.reduce((acc, cur) => acc.concat(cur), []);
// [1, 2, 3, [4, 5], 6] — 只扁平一层
管道函数:
const pipe =
(...fns) =>
(input) =>
fns.reduce((acc, fn) => fn(acc), input);
const transform = pipe(
(str) => str.trim(),
(str) => str.toLowerCase(),
(str) => str.replace(/\s+/g, '-')
);
transform(' Hello World '); // 'hello-world'
统计词频:
const words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple'];
const frequency = words.reduce((acc, word) => {
acc[word] = (acc[word] || 0) + 1;
return acc;
}, {});
// { apple: 3, banana: 2, cherry: 1 }
1.3 reduce 常见陷阱
忘记初始值:
// 空数组没有初始值会报错
[].reduce((acc, cur) => acc + cur); // TypeError
// 始终提供初始值
[].reduce((acc, cur) => acc + cur, 0); // 0
修改累加器:
// 修改原对象
const result = items.reduce((acc, item) => {
acc.push(item.id); // 修改了 acc
return acc;
}, []);
// 函数式风格(虽然 push 也可以,但更推荐不可变风格)
const result = items.reduce((acc, item) => [...acc, item.id], []);
// 注意:这种写法性能较差,大数据量时推荐用 push
2. flatMap — 映射后扁平化
2.1 基本语法
array.flatMap((element, index, array) => {
return newArrayOrElement;
});
flatMap 等价于 map 后跟 flat(1):
arr.flatMap(fn) === arr.map(fn).flat(1);
2.2 常见用法
一对多映射:
const sentences = ['Hello World', 'Good Morning'];
const words = sentences.flatMap((s) => s.split(' '));
// ['Hello', 'World', 'Good', 'Morning']
过滤 + 映射:
// 只保留正数并加倍
const numbers = [-2, -1, 0, 1, 2, 3];
const result = numbers.flatMap((n) => (n > 0 ? [n * 2] : []));
// [2, 4, 6]
提取嵌套属性:
const users = [
{ name: 'Alice', orders: [{ id: 1 }, { id: 2 }] },
{ name: 'Bob', orders: [{ id: 3 }] },
{ name: 'Charlie', orders: [] },
];
const orderIds = users.flatMap((user) => user.orders.map((o) => o.id));
// [1, 2, 3]
展开树结构:
const categories = [
{ name: 'Electronics', subcategories: ['Phones', 'Laptops'] },
{ name: 'Clothing', subcategories: ['Shirts', 'Pants'] },
];
const allCategories = categories.flatMap((cat) => [cat.name, ...cat.subcategories]);
// ['Electronics', 'Phones', 'Laptops', 'Clothing', 'Shirts', 'Pants']
3. 其他高阶方法
3.1 find 和 findIndex
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];
users.find((u) => u.id === 2); // { id: 2, name: 'Bob' }
users.findIndex((u) => u.id === 2); // 1
users.findLast((u) => u.name.startsWith('B')); // { id: 2, name: 'Bob' }
users.findLastIndex((u) => u.name.startsWith('B')); // 1
3.2 every 和 some
const ages = [18, 25, 30, 42];
ages.every((age) => age >= 18); // true — 全部满足
ages.some((age) => age > 40); // true — 至少一个满足
3.3 Array.from 的映射
// 创建范围数组
const range = Array.from({ length: 5 }, (_, i) => i * 2);
// [0, 2, 4, 6, 8]
// 去重
const unique = Array.from(new Set([1, 2, 2, 3, 3]));
// [1, 2, 3]
3.4 at() 方法
const arr = [10, 20, 30, 40, 50];
arr.at(0); // 10
arr.at(-1); // 50 — 负索引从末尾计数
arr.at(-2); // 40
4. 函数式组合
4.1 链式调用
const result = orders
.filter((order) => order.status === 'completed')
.map((order) => order.total)
.reduce((sum, total) => sum + total, 0);
4.2 自定义组合
const compose =
(...fns) =>
(arr) =>
fns.reduce((acc, fn) => fn(acc), arr);
const processNumbers = compose(
(arr) => arr.filter((n) => n > 0),
(arr) => arr.map((n) => n * 2),
(arr) => arr.reduce((sum, n) => sum + n, 0)
);
processNumbers([-1, 2, -3, 4, 5]); // 22