A

Array.reduce() 被 Goated 了🐐✨

2025-05-25

Array.reduce() 被 Goated 了🐐✨

标题说明了一切🐐。我想谈谈我最喜欢的 JavaScript 数组方法:Array.reduce()。我知道有很多人喜欢这个方法,但请听我说完。reduce() 不仅仅是一种方法;它是一种生活方式✨。

说实话,当我第一次接触 Reduce 时,确实有点儿吓人。我花了一段时间才能够自信地在代码中处处使用它。但当我真正做到了,它彻底改变了我的生活。突然之间,我可以轻松地对数组执行复杂的操作,将它们转换成我需要的任何格式。我的代码变得更快、更简洁了。

但别光听我的。让我来展示一下 reduce() 能实现的一些功能。现在就来深入研究 Array.reduce(),看看它为什么这么受欢迎吧!🐐

Array.reduce() 的 9 个用例🐐

用例 1:求和

reduce() 最直接的用例之一是对一组数字求和。假设你有一个整数数组,你想求出它们的和。

const numbers: number[] = [1, 2, 3, 4, 5];
const sum: number = numbers.reduce((acc, curr) => acc + curr, 0);
console.log(sum); // Output: 15
Enter fullscreen mode Exit fullscreen mode

轰!仅用一行代码,你就计算出了数组中所有元素的总和。累加器的初始值设置为 0,每次迭代时,我们将当前元素添加到累加器中。

** 额外提示:如果你选择省略初始值,reduce 将直接使用数组中的第一个元素。不过,我倾向于始终包含初始值,这样更容易阅读。

用例 2:展平数组

您是否曾经发现自己拥有一个数组并想过“我希望可以将其展平为一个数组”?

const nestedArray: number[][] = [[1, 2], [3, 4], [5, 6]];
const flattenedArray: number[] = nestedArray.reduce((acc, curr) => acc.concat(curr), []);
console.log(flattenedArray); // Output: [1, 2, 3, 4, 5, 6]
Enter fullscreen mode Exit fullscreen mode

在这个例子中,我们以一个空数组作为初始累加器的值。然后,在每次迭代中,我们使用 concat() 方法将当前子数组连接到累加器。最终,我们得到了一个完美的扁平化数组。

我知道你也可以用 来实现这一点Array.flat()。但是,了解如何使用 reduce 非常重要,以防你想对每个项目执行额外的操作。

用例 3:分组对象

假设您有一个对象数组,并且您想根据特定属性对它们进行分组。reduce() 是完成这项工作的完美工具。

interface Person {
  name: string;
  age: number;
}

const people: Person[] = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 25 },
  { name: 'Dave', age: 30 }
];

const groupedByAge: { [key: number]: Person[] } = people.reduce((acc, curr) => {
  if (!acc[curr.age]) {
    acc[curr.age] = [];
  }
  acc[curr.age].push(curr);
  return acc;
}, {});

console.log(groupedByAge);
/*
Output:
{
  '25': [{ name: 'Alice', age: 25 }, { name: 'Charlie', age: 25 }],
  '30': [{ name: 'Bob', age: 30 }, { name: 'Dave', age: 30 }]
}
*/
Enter fullscreen mode Exit fullscreen mode

在本例中,我们使用一个对象作为初始累加器的值。我们检查累加器是否已经包含当前年龄的属性。如果没有,则为该年龄创建一个空数组。然后将当前对象推送到相应的年龄数组中。最终,我们得到一个对象,其键是年龄,值是该年龄的人员数组。

现在您也可以了解较新的groupBy方法,但是,了解这个经过验证的经典方法很重要。

用例 4:创建查找图

我个人最喜欢的是使用 reduce() 从数组创建查找映射。它在性能和代码可读性方面具有颠覆性的意义。告别那些缓慢的 find() 或 filter() 调用吧。

interface Product {
  id: number;
  name: string;
  price: number;
}

const products: Product[] = [
  { id: 1, name: 'Laptop', price: 999 },
  { id: 2, name: 'Phone', price: 699 },
  { id: 3, name: 'Tablet', price: 499 },
];

const productMap: { [key: number]: Product } = products.reduce((acc, curr) => {
  acc[curr.id] = curr;
  return acc;
}, {});

console.log(productMap);
/*
Output:
{
  '1': { id: 1, name: 'Laptop', price: 999 },
  '2': { id: 2, name: 'Phone', price: 699 },
  '3': { id: 3, name: 'Tablet', price: 499 }
}
*/

// Accessing a product by ID
const laptop: Product = productMap[1];
console.log(laptop); // Output: { id: 1, name: 'Laptop', price: 999 }
Enter fullscreen mode Exit fullscreen mode

通过使用 reduce() 创建查找映射,您可以以恒定的时间复杂度通过唯一标识符访问元素。无需再循环遍历数组来查找特定项。

用例 5:计数发生次数

您是否曾经需要统计数组中元素的出现次数?reduce() 可以帮您搞定。

const fruits: string[] = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];

const fruitCounts: { [key: string]: number } = fruits.reduce((acc, curr) => {
  acc[curr] = (acc[curr] || 0) + 1;
  return acc;
}, {});

console.log(fruitCounts);
/*
Output:
{
  'apple': 3,
  'banana': 2,
  'orange': 1
}
*/
Enter fullscreen mode Exit fullscreen mode

在此示例中,我们初始化一个空对象作为累加器。对于数组中的每个水果,我们检查它是否已作为累加器对象的属性存在。如果存在,则将其计数加 1;否则,将其初始化为 1。结果是一个对象,告诉我们每个水果在数组中出现了多少次。

用例 6:组合函数

函数式编程爱好者一定会喜欢这个。reduce() 是一个强大的函数组合工具。你可以使用它来创建一个逐步转换数据的函数管道。

const add5 = (x: number): number => x + 5;
const multiply3 = (x: number): number => x * 3;
const subtract2 = (x: number): number => x - 2;

const composedFunctions: ((x: number) => number)[] = [add5, multiply3, subtract2];

const result: number = composedFunctions.reduce((acc, curr) => curr(acc), 10);
console.log(result); // Output: 43
Enter fullscreen mode Exit fullscreen mode

在这个例子中,我们有一个函数数组,希望按顺序执行这些函数,初始值为 10。我们使用 reduce() 来迭代这些函数,并将每个函数的结果作为下一个函数的输入。最终结果是按组合顺序执行所有函数的结果。

用例 7:实现简单的类似 Redux 的状态管理

如果你用过 Redux,你一定知道它在应用状态管理方面有多强大。猜猜怎么着?你可以用 reduce() 实现一个简单的类似 Redux 的状态管理系统。

interface State {
  count: number;
  todos: string[];
}

interface Action {
  type: string;
  payload?: any;
}

const initialState: State = {
  count: 0,
  todos: [],
};

const actions: Action[] = [
  { type: 'INCREMENT_COUNT' },
  { type: 'ADD_TODO', payload: 'Learn Array.reduce()' },
  { type: 'INCREMENT_COUNT' },
  { type: 'ADD_TODO', payload: 'Master TypeScript' },
];

const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case 'INCREMENT_COUNT':
      return { ...state, count: state.count + 1 };
    case 'ADD_TODO':
      return { ...state, todos: [...state.todos, action.payload] };
    default:
      return state;
  }
};

const finalState: State = actions.reduce(reducer, initialState);
console.log(finalState);
/*
Output:
{
  count: 2,
  todos: ['Learn Array.reduce()', 'Master TypeScript']
}
*/
Enter fullscreen mode Exit fullscreen mode

在这个例子中,我们有一个初始状态对象和一个动作数组。我们定义了一个 Reducer 函数,它接受当前状态和一个动作,并根据动作类型返回一个新状态。通过使用 Reduce(),我们可以按顺序将每个动作应用于状态,从而得到最终状态。这就像一个迷你版的 Redux。

用例 8:生成唯一值

有时,您可能有一个包含重复值的数组,并且您只需要提取唯一的值。reduce() 可以帮助您轻松完成此操作。

const numbers: number[] = [1, 2, 3, 2, 4, 3, 5, 1, 6];

const uniqueNumbers: number[] = numbers.reduce((acc, curr) => {
  if (!acc.includes(curr)) {
    acc.push(curr);
  }
  return acc;
}, []);

console.log(uniqueNumbers); // Output: [1, 2, 3, 4, 5, 6]
Enter fullscreen mode Exit fullscreen mode

这里,我们初始化一个空数组作为累加器。对于原始数组中的每个数字,我们使用 includes() 方法检查它是否已存在于累加器中。如果不存在,则将其推送到累加器数组中。最终结果是一个仅包含原始数组中唯一值的数组。

用例 9:计算平均值

想要计算一组数字的平均值吗?reduce() 可以帮你实现!

const grades: number[] = [85, 90, 92, 88, 95];

const average: number = grades.reduce((acc, curr, index, array) => {
  acc += curr;
  if (index === array.length - 1) {
    return acc / array.length;
  }
  return acc;
}, 0);

console.log(average); // Output: 90
Enter fullscreen mode Exit fullscreen mode

在这个例子中,我们将累加器初始化为 0。我们迭代每个成绩并将其添加到累加器中。当我们到达最后一个元素(使用索引和数组长度检查)时,我们将累加器除以总成绩数来计算平均值。

性能考虑🏎️

虽然 Array.reduce() 功能强大且用途广泛,但务必注意其潜在的性能缺陷,尤其是在处理大型数组或执行复杂操作时。一个常见的陷阱是在 reduce() 的每次迭代中创建新的对象或数组,这可能会导致过多的内存分配并影响性能。

例如,考虑以下代码:

const numbers: number[] = [1, 2, 3, 4, 5];

const doubledNumbers: number[] = numbers.reduce((acc, curr) => {
  return [...acc, curr * 2];
}, []);

console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]
Enter fullscreen mode Exit fullscreen mode

在这种情况下,我们使用扩展运算符 (...) 在每次迭代中创建一个新数组,这可能效率低下。相反,我们可以通过直接修改累加器数组来优化代码:

const numbers: number[] = [1, 2, 3, 4, 5];

const doubledNumbers: number[] = numbers.reduce((acc, curr) => {
  acc.push(curr * 2);
  return acc;
}, []);

console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]
Enter fullscreen mode Exit fullscreen mode

通过使用 push() 改变累加器数组,我们避免在每次迭代中创建新数组,从而获得更好的性能。

类似地,当处理对象时,直接改变累加器对象比使用扩展运算符创建新对象更有效:

const people: Person[] = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 25 },
  { name: 'Dave', age: 30 }
];

const groupedByAge: { [key: number]: Person[] } = people.reduce((acc, curr) => {
  if (!acc[curr.age]) {
    acc[curr.age] = [];
  }
  acc[curr.age].push(curr);
  return acc;
}, {});
Enter fullscreen mode Exit fullscreen mode

通过直接改变累加器对象,我们优化了 reduce() 操作的性能。

然而,值得注意的是,在某些情况下,在每次迭代中创建新的对象或数组可能是必要的,或者是为了提高可读性。根据你的具体用例和所处理的数据大小,在性能和代码清晰度之间取得平衡非常重要。

结论

就是这样。九个令人难以置信的用例展示了 Array.reduce() 的强大功能和多功能性。从求和到展平数组,从分组对象到创建查找映射,从计数出现次数到组合函数,甚至实现状态管理和计算平均值,都Array.reduce()证明了它是你 JS 工具包中一个强大的工具。

你觉得怎么样?你最喜欢的数组方法是什么?为什么?
感谢阅读,愿 reduce() 的力量与你同在。✨🐐✨

哦,最后再说一句厚颜无耻的话:😁
如果你在敏捷开发团队工作,请查看我的免费规划扑克和回顾应用程序 Kollabe

文章来源:https://dev.to/mattlewandowski93/arrayreduce-is-goated-1f1j
PREV
编写自文档代码
NEXT
JavaScript 代理的 7 个用例