0

0

如何使用 Underscore.js 处理嵌套数组并统计元素出现次数

心靈之曲

心靈之曲

发布时间:2025-09-30 13:24:02

|

843人浏览过

|

来源于php中文网

原创

如何使用 Underscore.js 处理嵌套数组并统计元素出现次数

本文旨在探讨如何利用 Underscore.js 高效地处理嵌套数组数据,并统计其中特定元素的出现频率。我们将介绍使用 _.countBy() 这一 Underscore.js 内置方法的最佳实践,并通过链式调用 _.map() 和 _.flatten() 来准备数据。同时,我们也会深入分析 _.reduce() 在此类场景下的正确用法,并纠正常见的逻辑错误,帮助开发者理解并掌握数据聚合技巧。

处理嵌套数组的数据聚合挑战

在日常开发中,我们经常会遇到需要从复杂数据结构中提取并聚合特定信息的需求。例如,给定一个包含多个对象的数组,每个对象又包含一个嵌套的数组,我们的目标可能是统计嵌套数组中元素的出现频率。以一个nfl球队数据为例:

var nflTeams = [
  { name: 'Kansas City Chiefs', playersFirstNames: ['Shane', 'Chad', 'Michael', 'Ronald', 'Blake', 'Noah'], champions: true },
  { name: 'Philadelphia Eagles', playersFirstNames: ['Jalen', 'Kenneth', 'Boston', 'Trey', 'Jack', 'Andre', 'Jack', 'Lane', 'Jason', 'Nakobe'], champions: false },
  { name: 'Cincinnati Bengals', playersFirstNames: ['Brandon', 'Joe', 'Chris', 'Joe', 'Tyler', 'Trenton', 'Trent', 'Mitchell', 'Alex', 'Trey', 'Ted'], champions: false },
  { name: 'San Francisco 49ers', playersFirstNames: ['Jimmy', 'Josh', 'Kyle', 'Jordan', 'Brandon', 'Danny', 'George', 'Tyler', 'Charlie', 'Jake', 'Nick', 'Nick', 'Kevin'], champions: false },
];

我们的目标是从所有球队中提取所有球员的名字,并统计每个名字出现的次数,最终生成一个类似 {'Joe': 2, 'Jimmy': 1, 'Jack': 2, ...} 的对象。Underscore.js 提供了多种方法来优雅地解决这类问题。

方案一:利用 Underscore.js 的 _.countBy() 简化统计

对于计数频率这类特定需求,Underscore.js 提供了 _.countBy() 方法,它专门用于根据给定迭代器(函数或属性名)对集合中的元素进行分组并计数。这是实现我们目标最简洁和高效的方法。

步骤一:扁平化嵌套数组

在使用 _.countBy() 之前,我们需要将所有球队的 playersFirstNames 数组合并成一个单一的、扁平化的球员名字列表。

  1. 使用原生 JavaScript 的 Array.prototype.flatMap() (如果环境支持) 如果你的开发环境支持 ES2019 及以上版本的 JavaScript,flatMap() 是一个非常方便的选择,它结合了 map 和 flatten 的功能。

    const allPlayerNames = nflTeams.flatMap(team => team.playersFirstNames);
    // console.log(allPlayerNames); // 输出一个包含所有球员名字的扁平数组
  2. 使用 Underscore.js 的 _.map() 和 _.flatten() 链式调用 这是更符合 Underscore.js 链式调用风格的方式,也是在不支持 flatMap() 环境下的通用做法。

    const allPlayerNames = _.chain(nflTeams)
      .map('playersFirstNames') // 提取所有球队的 playersFirstNames 数组
      .flatten()             // 将数组的数组扁平化成一个单一数组
      .value();              // 获取链式操作的最终结果
    // console.log(allPlayerNames); // 输出一个包含所有球员名字的扁平数组

步骤二:应用 _.countBy() 进行计数

一旦我们有了扁平化的球员名字列表,就可以直接将它传递给 _.countBy()。由于我们想要统计每个名字本身的出现次数,_.countBy() 不需要额外的迭代器函数,它会默认将数组中的每个元素作为键进行计数。

完整代码示例(结合 flatMap() 和 _.countBy()):

const nflTeams = [
  { name: 'Kansas City Chiefs', playersFirstNames: ['Shane', 'Chad', 'Michael', 'Ronald', 'Blake', 'Noah'], champions: true },
  { name: 'Philadelphia Eagles', playersFirstNames: ['Jalen', 'Kenneth', 'Boston', 'Trey', 'Jack', 'Andre', 'Jack', 'Lane', 'Jason', 'Nakobe'], champions: false },
  { name: 'Cincinnati Bengals', playersFirstNames: ['Brandon', 'Joe', 'Chris', 'Joe', 'Tyler', 'Trenton', 'Trent', 'Mitchell', 'Alex', 'Trey', 'Ted'], champions: false },
  { name: 'San Francisco 49ers', playersFirstNames: ['Jimmy', 'Josh', 'Kyle', 'Jordan', 'Brandon', 'Danny', 'George', 'Tyler', 'Charlie', 'Jake', 'Nick', 'Nick', 'Kevin'], champions: false },
];

const nameOccurrences = _.countBy(nflTeams.flatMap(team => team.playersFirstNames));
console.log(nameOccurrences);
/*
输出:
{
  Shane: 1, Chad: 1, Michael: 1, Ronald: 1, Blake: 1, Noah: 1,
  Jalen: 1, Kenneth: 1, Boston: 1, Trey: 2, Jack: 2, Andre: 1, Lane: 1, Jason: 1, Nakobe: 1,
  Brandon: 2, Joe: 2, Chris: 1, Tyler: 2, Trenton: 1, Trent: 1, Mitchell: 1, Alex: 1, Ted: 1,
  Jimmy: 1, Josh: 1, Kyle: 1, Jordan: 1, Danny: 1, George: 1, Charlie: 1, Jake: 1, Nick: 2, Kevin: 1
}
*/

完整代码示例(结合 Underscore 链式调用 map().flatten().countBy()):

const nflTeams = [
  { name: 'Kansas City Chiefs', playersFirstNames: ['Shane', 'Chad', 'Michael', 'Ronald', 'Blake', 'Noah'], champions: true },
  { name: 'Philadelphia Eagles', playersFirstNames: ['Jalen', 'Kenneth', 'Boston', 'Trey', 'Jack', 'Andre', 'Jack', 'Lane', 'Jason', 'Nakobe'], champions: false },
  { name: 'Cincinnati Bengals', playersFirstNames: ['Brandon', 'Joe', 'Chris', 'Joe', 'Tyler', 'Trenton', 'Trent', 'Mitchell', 'Alex', 'Trey', 'Ted'], champions: false },
  { name: 'San Francisco 49ers', playersFirstNames: ['Jimmy', 'Josh', 'Kyle', 'Jordan', 'Brandon', 'Danny', 'George', 'Tyler', 'Charlie', 'Jake', 'Nick', 'Nick', 'Kevin'], champions: false },
];

const nameOccurrences = _.chain(nflTeams)
  .map('playersFirstNames')
  .flatten()
  .countBy()
  .value();
console.log(nameOccurrences);
/*
输出:
{
  Shane: 1, Chad: 1, Michael: 1, Ronald: 1, Blake: 1, Noah: 1,
  Jalen: 1, Kenneth: 1, Boston: 1, Trey: 2, Jack: 2, Andre: 1, Lane: 1, Jason: 1, Nakobe: 1,
  Brandon: 2, Joe: 2, Chris: 1, Tyler: 2, Trenton: 1, Trent: 1, Mitchell: 1, Alex: 1, Ted: 1,
  Jimmy: 1, Josh: 1, Kyle: 1, Jordan: 1, Danny: 1, George: 1, Charlie: 1, Jake: 1, Nick: 2, Kevin: 1
}
*/

这两种方法都非常简洁高效,特别是第二种,完全符合使用 Underscore.js 链式调用的初衷。

方案二:深入理解并正确使用 _.reduce() 进行聚合

尽管 _.countBy() 是此场景下的最佳选择,但理解如何正确使用 _.reduce() 对于处理更复杂的聚合逻辑至关重要。_.reduce() 是一个非常强大的函数,它通过对集合中的每个元素应用一个回调函数,将集合“缩减”为单个值(例如一个对象、一个数组或一个数字)。

Skybox AI
Skybox AI

一键将涂鸦转为360°无缝环境贴图的AI神器

下载

分析常见 _.reduce() 错误

在尝试使用 _.reduce() 统计名字出现次数时,常见的错误是回调函数中的逻辑问题,尤其是在处理赋值和条件判断时。例如,一个常见的错误尝试可能如下:

// 错误的 reduce 尝试
var firstNameOccurence = _.chain (nflTeams)
  .map(function(team) {return team.playersFirstNames})
  .flatten()
  .reduce(function(newObject, firstName){
      // 错误逻辑:newObject[firstName] = 1 ? !newObject[firstName] : newObject[firstName] += 1;
      // 解释:
      // 1. `newObject[firstName] = 1` 是一个赋值操作,它的结果是赋的值(即 1)。
      // 2. `1` 在布尔上下文中为真,所以三元表达式的条件 `newObject[firstName] = 1` 始终为真。
      // 3. 结果是,回调函数将始终执行三元表达式的真分支:`!newObject[firstName]`。
      // 4. `newObject[firstName]` 刚被赋值为 1,所以 `!newObject[firstName]` 实际上是 `!1`,即 `false`。
      // 5. 因此,`reduce` 在第一次迭代后会返回 `false`,后续迭代中 `newObject` 不再是对象,导致错误或非预期行为。
      return newObject[firstName] = 1 ? !newObject[firstName] :  newObject[firstName] += 1;
  }, {})
  .value();
// console.log(firstNameOccurence); // 结果将是 true

这个例子清晰地展示了对 JavaScript 核心运算符(如赋值 =、逻辑非 ! 和三元运算符 ? :)理解不足可能导致的错误。reduce 回调函数必须始终返回累加器(这里是 newObject),而不是一个布尔值或其他非预期的类型。

修正 _.reduce() 的正确姿势

要正确使用 _.reduce() 来统计元素频率,我们需要确保:

  1. 回调函数始终返回累加器对象。
  2. 在累加器中正确地更新计数。

正确的 _.reduce() 回调函数逻辑应该像这样:

const nflTeams = [
  { name: 'Kansas City Chiefs', playersFirstNames: ['Shane', 'Chad', 'Michael', 'Ronald', 'Blake', 'Noah'], champions: true },
  { name: 'Philadelphia Eagles', playersFirstNames: ['Jalen', 'Kenneth', 'Boston', 'Trey', 'Jack', 'Andre', 'Jack', 'Lane', 'Jason', 'Nakobe'], champions: false },
  { name: 'Cincinnati Bengals', playersFirstNames: ['Brandon', 'Joe', 'Chris', 'Joe', 'Tyler', 'Trenton', 'Trent', 'Mitchell', 'Alex', 'Trey', 'Ted'], champions: false },
  { name: 'San Francisco 49ers', playersFirstNames: ['Jimmy', 'Josh', 'Kyle', 'Jordan', 'Brandon', 'Danny', 'George', 'Tyler', 'Charlie', 'Jake', 'Nick', 'Nick', 'Kevin'], champions: false },
];

const nameOccurrencesWithReduce = _.chain(nflTeams)
  .map('playersFirstNames')
  .flatten()
  .reduce((currObject, firstName) => {
    // 如果 currObject 中不存在 firstName,则将其初始化为 0,然后加 1
    // 否则,直接将其值加 1
    currObject[firstName] = (currObject[firstName] || 0) + 1;
    return currObject; // 始终返回累加器对象
  }, {}) // 初始累加器是一个空对象
  .value();

console.log(nameOccurrencesWithReduce);
/*
输出:
{
  Shane: 1, Chad: 1, Michael: 1, Ronald: 1, Blake: 1, Noah: 1,
  Jalen: 1, Kenneth: 1, Boston: 1, Trey: 2, Jack: 2, Andre: 1, Lane: 1, Jason: 1, Nakobe: 1,
  Brandon: 2, Joe: 2, Chris: 1, Tyler: 2, Trenton: 1, Trent: 1, Mitchell: 1, Alex: 1, Ted: 1,
  Jimmy: 1, Josh: 1, Kyle: 1, Jordan: 1, Danny: 1, George: 1, Charlie: 1, Jake: 1, Nick: 2, Kevin: 1
}
*/

在这个修正后的 reduce 实现中,currObject[firstName] = (currObject[firstName] || 0) + 1; 确保了当 firstName 第一次出现时,其计数被初始化为 1;之后每次出现,计数都会递增。关键在于,我们每次都返回了 currObject,保证了 reduce 的累加过程是正确的。

关于效率的额外说明: 虽然上述 reduce 方法有效,但如果每次迭代都创建一个新对象 ({...currObject, [firstName]: (currObject[firstName] || 0) + 1}),虽然更具函数式编程风格,但在处理大量数据时,其性能会低于直接修改 currObject 的方式,因为它会创建大量中间对象。在 Underscore.js 的 reduce 场景下,直接修改累加器是常见且高效的做法。

总结与最佳实践

  • 选择合适的工具 对于统计元素出现频率这类特定需求,Underscore.js 的 _.countBy() 是最直接、最简洁且性能最优的选择。它专门为此目的而设计,避免了手动实现计数逻辑的复杂性。
  • 理解 _.reduce() 的强大与通用性: _.reduce() 是一个非常强大的通用聚合工具,适用于各种将集合缩减为单一值的场景。然而,它的使用需要对回调函数的逻辑和累加器的管理有清晰的理解。
  • 掌握 JavaScript 核心运算符: 在编写 reduce 回调函数时,对 JavaScript 的赋值运算符 (=)、逻辑运算符 (&&, ||, !) 和三元运算符 (? :) 的正确理解至关重要,以避免引入难以发现的逻辑错误。
  • 链式调用提升可读性: Underscore.js 的 _.chain() 机制能够让一系列数据转换和聚合操作变得流畅且易于阅读。

通过本文的介绍,您应该能够熟练地使用 Underscore.js 处理嵌套数组并统计元素出现次数,并理解在不同场景下选择 _.countBy() 或 _.reduce() 的最佳实践。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1502

2023.10.24

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

232

2024.02.23

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

87

2025.10.17

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

87

2025.10.17

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

539

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

21

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

28

2026.01.06

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

75

2025.09.05

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

0

2026.01.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 4.3万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.5万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号