0

0

JavaScript 对象数组重构:将扁平结构转换为嵌套分组

霞舞

霞舞

发布时间:2025-11-25 16:38:02

|

912人浏览过

|

来源于php中文网

原创

JavaScript 对象数组重构:将扁平结构转换为嵌套分组

本文详细介绍了如何使用javascript的`array.prototype.reduce`方法,将一个包含复合键的扁平对象数组,高效地重构为按特定字段分组的嵌套结构。通过解析复合字符串并条件性地创建或更新分组,此教程提供了一种清晰、可维护的解决方案,适用于处理复杂数据转换场景。

需求背景与问题描述

在JavaScript开发中,我们经常需要对数据结构进行转换,以适应不同的业务逻辑或前端展示需求。一个常见的场景是将一个包含复合键的扁平对象数组,重构为按键分组的嵌套结构。

假设我们有以下原始数据结构:

const input = [{
  "type": "group1@action1",
  "label": "labelA",
  "placeholders": ["b", "a", "r"]
}, {
  "type": "group1@action2",
  "label": "labelB",
  "placeholders": ["x", "y", "z"]
}, {
  "type": "group2@action123",
  "label": "labelC",
  "placeholders": ["a", "b", "c"]
}];

我们的目标是将其转换为如下的嵌套结构:

[
  {
    "group": "group1",
    "items": [
      {
        "action": "action1",
        "label": "labelA",
        "placeholders": ["b", "a", "r"]
      },
      {
        "action": "action2", // 注意这里是action2,原问题描述中此处有误,应为action2
        "label": "labelB",
        "placeholders": ["x", "y", "z"]
      }
    ]
  },
  {
    "group": "group2",
    "items": [
      {
        "action": "action123",
        "label": "labelC",
        "placeholders": ["a", "b", "c"]
      }
    ]
  }
]

可以看到,转换的核心在于:

立即学习Java免费学习笔记(深入)”;

  1. 根据 type 字段中的 @ 符号,将其拆分为 group 和 action 两个独立的字段。
  2. 将具有相同 group 的对象归类到一个 items 数组中。
  3. 每个 item 对象只包含 action、label 和 placeholders 字段,不再包含原始的 type 字段。

初始尝试与常见误区

在处理这类数据转换时,开发者可能会尝试使用 Map 或简单的 forEach 循环进行分组。例如,以下代码尝试使用 Map 进行分组:

var outputMap = new Map();
input.forEach(element => {
    // 错误解析 action,此处的 substring 逻辑不正确
    const group = element.type.substring(0, element.type.indexOf('@'));
    // 原始代码的 action 解析有误,应从 '@' 之后开始
    const action = element.type.substring(element.type.indexOf('@') + 1); // 修正后的解析

    if (!outputMap.has(group)) {
        // 直接存储原始 element,未进行字段转换
        outputMap.set(group, [{
            action: action, // 修正后的 action
            label: element.label,
            placeholders: element.placeholders
        }]);
    } else {
        outputMap.get(group).push({
            action: action, // 修正后的 action
            label: element.label,
            placeholders: element.placeholders
        });
    }
});

// console.log(Object.fromEntries(outputMap));
/*
实际输出(修正解析后):
{
  group1: [{
    action: "action1",
    label: "labelA",
    placeholders: ["b", "a", "r"]
  }, {
    action: "action2",
    label: "labelB",
    placeholders: ["x", "y", "z"]
  }],
  group2: [{
    action: "action123",
    label: "labelC",
    placeholders: ["a", "b", "c"]
  }]
}
*/

虽然上述 Map 方法(经过 action 解析修正后)可以实现按 group 分组,但它最终生成的是一个以 group 为键的对象,而不是一个包含 group 字段的数组。要达到目标结构,还需要额外的步骤将 Map 转换为目标数组格式,并且在 Map 内部存储时需要对每个 item 进行字段提取和重构。

使用 Array.prototype.reduce 进行高效重构

Array.prototype.reduce() 方法是处理这类数据转换的强大工具。它遍历数组中的每个元素,并使用一个回调函数将所有元素归约为单个输出值(在这里是一个新的数组)。

以下是使用 reduce 实现目标转换的完整代码:

const input = [
  {
    "type": "group1@action1",
    "label": "labelA",
    "placeholders": ["b", "a", "r"]
  },
  {
    "type": "group1@action2",
    "label": "labelB",
    "placeholders": ["x", "y", "z"]
  },
  {
    "type": "group2@action123",
    "label": "labelC",
    "placeholders": ["a", "b", "c"]
  }
];

const output = input.reduce((accumulator, currentItem) => {
  // 1. 解析 type 字段,使用 split('@') 更简洁和健壮
  const [group, action] = currentItem.type.split("@");

  // 2. 查找累加器中是否已存在当前 group
  const existingGroup = accumulator.find(groupItem => groupItem.group === group);

  if (existingGroup) {
    // 3. 如果 group 已存在,将当前项添加到其 items 数组中
    existingGroup.items.push({
      action,
      label: currentItem.label,
      placeholders: currentItem.placeholders
    });
  } else {
    // 4. 如果 group 不存在,创建一个新的 group 对象并添加到累加器中
    accumulator.push({
      group,
      items: [
        {
          action,
          label: currentItem.label,
          placeholders: currentItem.placeholders
        }
      ]
    });
  }

  // 5. 返回更新后的累加器
  return accumulator;
}, []); // reduce 的初始值是一个空数组,用于存储最终的 group 列表

console.log(output);

代码解析与工作原理

  1. input.reduce((accumulator, currentItem) => { ... }, []);

    Peppertype.ai
    Peppertype.ai

    高质量AI内容生成软件,它通过使用机器学习来理解用户的需求。

    下载
    • reduce 方法接收两个参数:一个回调函数和一个初始值。
    • accumulator (累加器) 是 reduce 过程中累积的结果。在这里,它是一个数组,最终将成为我们的 output。初始值被设置为 [] (一个空数组)。
    • currentItem 是 input 数组中当前正在处理的元素。
  2. const [group, action] = currentItem.type.split("@");

    • 这一行利用了ES6的数组解构赋值。currentItem.type.split("@") 会将字符串 "group1@action1" 分割成 ["group1", "action1"] 这样的数组。
    • 然后,[group, action] 会分别接收数组中的第一个和第二个元素,从而简洁地提取出 group 和 action。这种方法比 substring 更为健壮,因为它不依赖于 @ 符号的位置,只要存在 @ 即可正确分割。
  3. const existingGroup = accumulator.find(groupItem => groupItem.group === group);

    • Array.prototype.find() 方法用于在 accumulator 数组中查找一个元素。
    • 它会检查 accumulator 中的每个 groupItem,看其 group 属性是否与当前 currentItem 解析出的 group 相匹配。
    • 如果找到匹配项,existingGroup 将引用该对象;否则,existingGroup 将为 undefined。
  4. 条件分支 (if (existingGroup) { ... } else { ... })

    • 如果 existingGroup 存在 (即已找到相同的 group):
      • existingGroup.items.push(...):将当前 currentItem 的相关信息(action、label、placeholders)构造成一个新的对象,并推入到 existingGroup 的 items 数组中。
    • 如果 existingGroup 不存在 (即这是新的 group):
      • accumulator.push(...):创建一个新的 group 对象。
      • 这个新对象包含 group 属性和 items 数组。items 数组的初始值是一个只包含当前 currentItem 信息的对象。
      • 将这个新的 group 对象推入到 accumulator 数组中。
  5. return accumulator;

    • 每次回调函数执行完毕后,必须返回更新后的 accumulator。这个返回值将作为下一次迭代的 accumulator。

注意事项与最佳实践

  • 健壮性: split('@') 比 substring 更推荐,因为它能正确处理 @ 符号位置不确定的情况,或者在没有 @ 符号时返回包含原始字符串的数组。如果 type 字段可能不包含 @,需要增加额外的错误处理逻辑(例如,检查 action 是否为 undefined)。

  • 性能: 在 reduce 内部使用 find 方法,在最坏情况下(所有元素都属于不同的组),其时间复杂度可能接近 O(n^2),因为每次 find 操作都需要遍历 accumulator。对于非常大的数据集,可以考虑使用一个临时的 Map 或 Object 来缓存 group 到其 items 数组的引用,从而将查找时间优化到 O(1),将总时间复杂度降低到 O(n)。

    // 优化后的 reduce (使用 Map 缓存)
    const outputOptimized = Array.from(input.reduce((map, currentItem) => {
        const [group, action] = currentItem.type.split("@");
        let groupData = map.get(group);
    
        if (!groupData) {
            groupData = {
                group,
                items: []
            };
            map.set(group, groupData);
        }
    
        groupData.items.push({
            action,
            label: currentItem.label,
            placeholders: currentItem.placeholders
        });
    
        return map;
    }, new Map()).values()); // 从 Map 中提取值并转换为数组
    
    // console.log(outputOptimized);

    上述优化后的代码首先使用 Map 进行分组,最后通过 Array.from(map.values()) 将 Map 中的值转换为目标数组结构,从而将时间复杂度优化到 O(n)。

  • 可读性: 尽管 reduce 是一种非常强大的方法,但对于不熟悉函数式编程的开发者来说,其逻辑可能不如 forEach 循环结合外部变量那么直观。在团队协作中,应根据团队的熟悉程度和代码规范选择最合适的方案。

总结

通过本教程,我们学习了如何利用 Array.prototype.reduce 方法,结合字符串解析和条件逻辑,将扁平化的 JavaScript 对象数组高效地重构为所需的嵌套分组结构。这种模式在数据处理和前端组件数据准备中非常常见。理解 reduce 的工作原理及其在复杂数据转换中的应用,是提升 JavaScript 开发技能的关键一步。在实际项目中,根据数据规模和性能要求,可以选择标准 reduce 或结合 Map 进行优化的 reduce 方案。

热门AI工具

更多
DeepSeek
DeepSeek

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

豆包大模型
豆包大模型

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

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

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

文心一言
文心一言

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

讯飞写作
讯飞写作

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

即梦AI
即梦AI

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

ChatGPT
ChatGPT

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

相关专题

更多
es6新特性
es6新特性

es6新特性有:1、块级作用域变量;2、箭头函数;3、模板字符串;4、解构赋值;5、默认参数;6、 扩展运算符;7、 类和继承;8、Promise。本专题为大家提供es6新特性的相关的文章、下载、课程内容,供大家免费下载体验。

106

2023.07.17

es6新特性有哪些
es6新特性有哪些

es6的新特性有:1、块级作用域;2、箭头函数;3、解构赋值;4、默认参数;5、扩展运算符;6、模板字符串;7、类和模块;8、迭代器和生成器;9、Promise对象;10、模块化导入和导出等等。本专题为大家提供es6新特性的相关的文章、下载、课程内容,供大家免费下载体验。

197

2023.08.04

JavaScript ES6新特性
JavaScript ES6新特性

ES6是JavaScript的根本性升级,引入let/const实现块级作用域、箭头函数解决this绑定问题、解构赋值与模板字符串简化数据处理、对象简写与模块化提升代码可读性与组织性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

233

2025.12.24

if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

847

2023.08.22

php中foreach用法
php中foreach用法

本专题整合了php中foreach用法的相关介绍,阅读专题下面的文章了解更多详细教程。

267

2025.12.04

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

564

2023.09.20

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

761

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

221

2023.09.04

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

热门下载

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

精品课程

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

共58课时 | 6.1万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 3.4万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

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

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