javascript
数组内置方法综合运用
By AI-Writer 12 min read
JavaScript 数组提供了 30+ 个内置方法,但真正让代码产生质变的是方法的组合运用。本章聚焦高频方法的进阶用法、组合模式以及性能陷阱。
遍历方法的选择
方法速查
| 方法 | 返回值 | 是否改变原数组 | 适用场景 |
|---|---|---|---|
forEach | undefined | 否 | 副作用操作(打印、修改外部变量) |
map | 新数组 | 否 | 一对一转换 |
filter | 新数组 | 否 | 按条件筛选 |
find | 首个匹配项 / undefined | 否 | 查找单个元素 |
findIndex | 首个匹配索引 / -1 | 否 | 查找位置 |
some | boolean | 否 | 是否存在满足条件的元素 |
every | boolean | 否 | 是否全部满足条件 |
reduce | 任意类型 | 否 | 聚合、映射+过滤的复合操作 |
sort | 排序后的原数组 | 是 | 排序(注意是原地排序!) |
sort、reverse、splice会改变原数组,其余方法都是纯函数。
forEach 的局限
forEach 不能 break 或 return 跳出循环,也无法 await 异步操作(不会等待 Promise 完成):
javascript
const ids = [1, 2, 3];
// ❌ 错误:不会等待异步操作
ids.forEach(async (id) => {
const data = await fetch(`/api/${id}`); // 并发执行,不等待
});
// ✅ 正确:使用 for...of
for (const id of ids) {
const data = await fetch(`/api/${id}`); // 顺序执行,等待完成
}组合模式
管道式处理
将多个数组方法链式组合,形成数据转换管道:
javascript
const users = [
{ name: 'Alice', age: 28, active: true },
{ name: 'Bob', age: 17, active: true },
{ name: 'Carol', age: 35, active: false },
{ name: 'Dave', age: 22, active: true },
];
// 获取所有活跃成年用户的姓名,按年龄排序
const result = users
.filter(u => u.active) // 筛选活跃用户
.filter(u => u.age >= 18) // 筛选成年人
.map(u => ({ name: u.name, age: u.age })) // 提取字段
.sort((a, b) => a.age - b.age); // 按年龄升序
console.log(result);
// [{ name: 'Dave', age: 22 }, { name: 'Alice', age: 28 }]reduce:从累加到万能转换
reduce 是最强大的数组方法,它可以实现 map、filter 甚至更多:
javascript
const nums = [1, 2, 3, 4, 5];
// 基础用法:求和
const sum = nums.reduce((acc, n) => acc + n, 0); // 15
// 替代 map + filter:在一次遍历中完成
const doubledEvens = nums.reduce((acc, n) => {
if (n % 2 === 0) acc.push(n * 2);
return acc;
}, []); // [4, 8]
// 分组统计
const fruits = ['apple', 'banana', 'apricot', 'blueberry', 'cherry'];
const byFirstLetter = fruits.reduce((acc, fruit) => {
const first = fruit[0];
acc[first] = acc[first] || [];
acc[first].push(fruit);
return acc;
}, {});
// { a: ['apple', 'apricot'], b: ['banana', 'blueberry'], c: ['cherry'] }flatMap:映射+扁平化
flatMap 先执行 map,再将结果展平一层,是处理嵌套结构的利器:
javascript
const sentences = ['Hello world', 'JavaScript is fun'];
// 提取所有单词
const words = sentences.flatMap(s => s.split(' '));
// ['Hello', 'world', 'JavaScript', 'is', 'fun']
// 过滤+映射(去掉空值)
const items = [1, 2, 3, 4];
const evensDoubled = items.flatMap(n =>
n % 2 === 0 ? [n * 2] : [] // 空数组会被展平后消失
);
// [4, 8]实战案例
多维数据转换
处理 API 返回的嵌套数据是日常高频场景:
javascript
const apiResponse = {
departments: [
{
name: 'Engineering',
teams: [
{ name: 'Frontend', members: ['Alice', 'Bob'] },
{ name: 'Backend', members: ['Carol', 'Dave'] },
]
},
{
name: 'Design',
teams: [
{ name: 'UI', members: ['Eve'] },
{ name: 'UX', members: ['Frank', 'Grace'] },
]
}
]
};
// 提取所有成员名单,带上部门信息
const allMembers = apiResponse.departments.flatMap(dept =>
dept.teams.flatMap(team =>
team.members.map(member => ({
name: member,
department: dept.name,
team: team.name,
}))
)
);
// 7 条记录,每条包含 name、department、team去重与交集并集
javascript
const arr1 = [1, 2, 3, 4];
const arr2 = [3, 4, 5, 6];
// 并集(去重)
const union = [...new Set([...arr1, ...arr2])]; // [1, 2, 3, 4, 5, 6]
// 交集
const intersection = arr1.filter(x => arr2.includes(x)); // [3, 4]
// 差集(在 arr1 中但不在 arr2 中)
const difference = arr1.filter(x => !arr2.includes(x)); // [1, 2]
// 对象数组去重(按某个键)
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 1, name: 'Alice Duplicate' },
];
const uniqueById = [...new Map(users.map(u => [u.id, u])).values()];
// [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]性能陷阱
1. 大数组的链式操作
每次链式调用都会创建一个新数组,大数据量时内存开销巨大:
javascript
const bigArray = Array.from({ length: 1000000 }, (_, i) => i);
// ❌ 内存不友好:创建 3 个中间数组
const result = bigArray
.filter(n => n % 2 === 0)
.map(n => n * 2)
.slice(0, 10);
// ✅ 使用生成器实现惰性求值(按需计算)
function* lazyPipeline(arr) {
for (const n of arr) {
if (n % 2 === 0) {
yield n * 2;
}
}
}
const lazyResult = [...lazyPipeline(bigArray)].slice(0, 10);
// 只计算前 10 个需要的元素2. find + includes 的 O(n²) 陷阱
javascript
const orders = [{ id: 1 }, { id: 2 }, /* ...10000 条 */];
const validIds = [/* ...5000 个 ID */];
// ❌ O(n * m):每次 includes 都遍历 validIds
const validOrders = orders.filter(o => validIds.includes(o.id));
// ✅ O(n + m):先用 Set 转换
const validSet = new Set(validIds);
const validOrdersFast = orders.filter(o => validSet.has(o.id));3. sort 的隐蔽副作用
javascript
const items = [{ name: 'Bob' }, { name: 'Alice' }];
// ❌ 原数组被修改!
items.sort((a, b) => a.name.localeCompare(b.name));
console.log(items); // [{ name: 'Alice' }, { name: 'Bob' }]
// ✅ 先复制再排序
const sorted = [...items].sort((a, b) => a.name.localeCompare(b.name));4. reduce 的初始值陷阱
javascript
const nums = [];
// ❌ 空数组无初始值会报错
// const sum = nums.reduce((a, b) => a + b); // TypeError!
// ✅ 始终提供初始值
const sum = nums.reduce((a, b) => a + b, 0); // 0排序进阶
稳定排序与自定义比较
ES2019 起 sort 是稳定排序(相等元素的相对顺序保持不变):
javascript
const items = [
{ name: 'A', priority: 2 },
{ name: 'B', priority: 1 },
{ name: 'C', priority: 2 },
];
// 按 priority 升序,相同 priority 保持原始顺序(稳定排序)
items.sort((a, b) => a.priority - b.priority);
// [{ name: 'B', priority: 1 }, { name: 'A', priority: 2 }, { name: 'C', priority: 2 }]
// 多级排序:先按年龄降序,再按姓名升序
const users = [
{ name: 'Bob', age: 30 },
{ name: 'Alice', age: 30 },
{ name: 'Carol', age: 25 },
];
users.sort((a, b) => {
if (b.age !== a.age) return b.age - a.age;
return a.name.localeCompare(b.name);
});toSorted / toReversed(ES2023)
不改变原数组的排序方法:
javascript
const arr = [3, 1, 4, 1, 5];
const sorted = arr.toSorted((a, b) => a - b); // [1, 1, 3, 4, 5]
console.log(arr); // [3, 1, 4, 1, 5] —— 原数组不变小结
- 优先选择纯函数方法:
map、filter、reduce组合出声明式代码 - 链式调用注意内存:大数据量考虑惰性求值或
for...of循环 - Set 优化查找:对象数组的交集/差集操作前先将小数组转为
Set - 永远给 reduce 初始值:避免空数组导致运行时错误
- 用
toSorted/toReversed替代sort/reverse,避免副作用
#javascript
#array
#函数式编程
#性能
评论
A
Written by
AI-Writer