发布于 2026-01-06 1 阅读
0

快速提示:使用 .reduce() 将数组转换为对象 反馈 💬 AWS AI 直播!

小技巧:使用 .reduce() 将数组转换为对象

反馈💬

AWS AI 直播!

我就不赘述冗长乏味的引言了,文章正文如下:

假设你有一个这样的数组:

[
    {id: 1, category: "frontend", title: "All About That Sass"},
    {id: 2, category: "backend", title: "Beam me up, Scotty: Apache Beam tips"},
    {id: 3, category: "frontend", title: "Sanitizing HTML: Going antibactirial on XSS attacks"}
]
Enter fullscreen mode Exit fullscreen mode

您希望获得一个以类别为键的对象,并将其映射到具有该类别的文章 ID,如下所示:

{
    frontend: [1, 3],
    backend: [2]
}
Enter fullscreen mode Exit fullscreen mode

你可以利用我们的朋友Array.prototype.reduce来做这件事。

const posts = [
    {id: 1, category: "frontend", title: "All About That Sass"},
    {id: 2, category: "backend", title: "Beam me up, Scotty: Apache Beam tips"},
    {id: 3, category: "frontend", title: "Sanitizing HTML: Going antibactirial on XSS attacks"}
];

const categoryPosts = posts.reduce((acc, post) => {
    let {id, category} = post;
    return {...acc, [category]: [...(acc[category] || []), id]};
}, {});
Enter fullscreen mode Exit fullscreen mode

好的,我们来看看效果如何。

我把它想象reduce成把我的数组变成了一个管道。这个管道接收一个初始值,并将数组中的每个值作为单独的步骤应用到管道中,然后返回新值。这个从一步传递到下一步的值通常被称为累加器,因为它会累积管道中的变化。累加器的初始值作为第二个参数传递给它reduce。在本例中,它是一个空对象。那么,数组中的元素是如何应用到累加器的呢?这取决于你传递给它的reduce第一个参数的函数。该函数返回的值将用作累加器的新值。

(acc, post) => {
    let {id, category} = post;
    return {...acc, [category]: [...(acc[category] || [])], id]};
}
Enter fullscreen mode Exit fullscreen mode

此函数以累加器作为第一个参数,以数组中的一个元素作为第二个参数。第一行使用对象解构将帖子的分类和 ID 提取到各自的变量中。这样做是为了方便我们使用简洁的变量名,使下一行代码更加清晰。

return {...acc, [category]: [...(acc[category] || [])], id]};
Enter fullscreen mode Exit fullscreen mode

我在这里使用了很多 ES6 语法,可能不是每个人都熟悉,所以让我们深入了解一下。

return {...acc}
Enter fullscreen mode Exit fullscreen mode

如果我们只返回这个值,实际上就等于返回了累加器的初始值,因为...前面的 `this` 被称为扩展运算符(spread)。在对象字面量中,它会获取给定对象的所有属性和值,并将它们放入新创建的对象中。所以上面这行代码的作用就是获取累加器的所有属性,并将它们放入我们返回的对象中。

return {...acc, [category]: [...(acc[category] || [])], id]};
Enter fullscreen mode Exit fullscreen mode

接下来你可能会注意到这个[category]:语法。这是一个计算属性名。其理念是,你可以在对象字面量中定义一个属性,而无需预先知道属性名。在上面的代码行中,属性名就是类别名称。

我们希望这个属性最终包含一个数组,其中包含所有属于此分类的帖子的 ID,所以让我们来看看我们给这个属性赋予的值:

[...(acc[category] || [])], id]}
Enter fullscreen mode Exit fullscreen mode

这里我们再次看到了扩展运算符,但这次是在数组字面量中。与对象扩展运算符类似,它会从给定的数组中提取所有值,并将它们如同写入数组字面量一样,插入到新创建的数组的相应位置。

这为我们提供了一种非常简洁的方式来定义一个数组,这个数组就是在另一个数组的基础上附加一个或多个值。

const a = [1, 2, 3];
const b = [...a, 4]; // b = [1, 2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

所以,在我们的帖子示例中,我们希望将帖子的 ID 添加到累加器已有的 ID 列表中,因此我们只需这样写:

[...acc[category], id]}
Enter fullscreen mode Exit fullscreen mode

但是,如果我们的累加器中还没有该类别下的任何帖子怎么办?(所有类别在开始时都会如此)那么结果acc[category]就会是undefined,而扩展语法只适用于可迭代值,例如数组,所以我们会得到一个TypeError

[...(acc[category] || [])], id]}
Enter fullscreen mode Exit fullscreen mode

因此,我们改为使用表达式acc[category] || [],(用花括号括起来,这样扩展语法就能应用于整个表达式。该||运算符在第一个值是假值(实际上undefined就是假值)时返回第二个值,所以如果我们的累加器中没有任何具有给定类别的帖子,我们就展开空数组,这样在新 ID 之前就不会添加任何值。

那么,让我们把所有内容整合起来:

const posts = [
    {id: 1, category: "frontend", title: "All About That Sass"},
    {id: 2, category: "backend", title: "Beam me up, Scotty: Apache Beam tips"},
    {id: 3, category: "frontend", title: "Sanitizing HTML: Going antibactirial on XSS attacks"}
];

const categoryPosts = posts.reduce((acc, post) => {
    let {id, category} = post;
    return {...acc, [category]: [...(acc[category] || []), id]};
}, {});
Enter fullscreen mode Exit fullscreen mode

调用reduceposts 数组,并将空对象作为初始累加器,对于每个帖子,我们:

  • 提取帖子 ID 和分类
  • 获取累加器的所有现有属性,并应用返回值。
  • 如果累加器中已经存在一个包含文章所属类别 ID 的数组,则将文章 ID 添加到该数组中。否则,为该类别创建一个新的空数组,并将文章 ID 添加到该数组中。
  • 传递给函数的返回值redused将用作数组中下一个帖子的累加器,并reduce在处理完所有帖子后返回。

反馈💬

这是我第一篇面向初学者的文章,非常欢迎大家提出任何建设性的反馈意见!我觉得这篇文章用了太多 ES6 语法,反而显得过于复杂,而且我还觉得有必要一一解释。我应该尽量减少语法的使用,只专注于使用 ES6reduce的核心概念。我可能还会写一篇更精简的版本,但目前就先这样吧。

对于精通函数式编程的人来说,这种用法reduce可能非常显而易见。但我大部分的编程生涯都花在了编写过程式和面向对象代码上。Map 对我来说很快就变得很直观,但我仍然时不时地会恍然大悟,发现它还有更多用途reduce

文章来源:https://dev.to/_bigblind/quick-tip-transform-an-array-into-an-object-using-reduce-2gh6