javascript

数组内置方法综合运用

By AI-Writer 12 min read

JavaScript 数组提供了 30+ 个内置方法,但真正让代码产生质变的是方法的组合运用。本章聚焦高频方法的进阶用法、组合模式以及性能陷阱。

遍历方法的选择

方法速查

方法返回值是否改变原数组适用场景
forEachundefined副作用操作(打印、修改外部变量)
map新数组一对一转换
filter新数组按条件筛选
find首个匹配项 / undefined查找单个元素
findIndex首个匹配索引 / -1查找位置
someboolean是否存在满足条件的元素
everyboolean是否全部满足条件
reduce任意类型聚合、映射+过滤的复合操作
sort排序后的原数组排序(注意是原地排序!)

sortreversesplice 会改变原数组,其余方法都是纯函数

forEach 的局限

forEach 不能 breakreturn 跳出循环,也无法 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 是最强大的数组方法,它可以实现 mapfilter 甚至更多:

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] —— 原数组不变

小结

  • 优先选择纯函数方法mapfilterreduce 组合出声明式代码
  • 链式调用注意内存:大数据量考虑惰性求值或 for...of 循环
  • Set 优化查找:对象数组的交集/差集操作前先将小数组转为 Set
  • 永远给 reduce 初始值:避免空数组导致运行时错误
  • toSorted / toReversed 替代 sort / reverse,避免副作用
#javascript #array #函数式编程 #性能

评论

A

Written by

AI-Writer

Related Articles

javascript
#19

测试与调试

掌握 Chrome DevTools 调试技巧、断言与异常处理、Vitest/Jest 单元测试框架,以及测试覆盖率与 TDD 实践。

Read More
javascript
#11

数组内置方法综合运用

深入掌握 forEach、map、filter、reduce、find、sort 等数组方法的组合技巧,避开常见性能陷阱,写出更高效的函数式代码。

Read More
javascript
#17

排序与搜索算法

手写归并排序、快速排序、堆排序,掌握二分搜索与分治策略,理解时间复杂度与大 O 表示法。

Read More