如何按指定key对对象数组分组,生成带层级结构的嵌套菜单数组?
扁平菜单数组转树形结构实现方案
原有代码错误说明
- 查找父节点时字段名大小写错误:
_it.Id应为_it.id,JavaScript 对象键名大小写敏感 - 每次遍历子节点都会重新初始化
childrenData数组,导致同一父节点的多个子项被覆盖,无法累加存储 - 赋值时使用了未定义的变量
child,应替换为你声明的childrenData - 每次调用
find查找父节点的时间复杂度为O(n),整体复杂度为O(n²),菜单数量较多时效率低下
高效实现方案
先通过映射表存储id和菜单对象的对应关系,两次遍历即可完成转换,整体时间复杂度为O(n):
let menus = [ {id: 1, parentId: 0, text: 'Home'}, {id: 2, parentId: 0, text: 'About'}, {id: 3, parentId: 0, text: 'Product'}, {id: 4, parentId: 3, text: 'Product Category 1'}, {id: 5, parentId: 3, text: 'Product Category 2'}, {id: 6, parentId: 3, text: 'Product Category 3'}, {id: 7, parentId: 0, text: 'Contact'}, {id: 8, parentId: 7, text: 'Contact child 1'}, {id: 9, parentId: 0, text: 'Blog'}, ] const menuMap = {} const result = [] // 第一次遍历:建立id到菜单对象的映射,提前初始化child字段 menus.forEach(item => { // 浅拷贝原对象,避免修改原始数据,不需要保留原数组可直接赋值为item menuMap[item.id] = {...item, child: menuMap[item.id]?.child || [] } }) // 第二次遍历:区分根节点和子节点,组装树形结构 menus.forEach(item => { if (item.parentId === 0) { result.push(menuMap[item.id]) } else { menuMap[item.parentId].child.push(menuMap[item.id]) } }) console.log(result) // 输出结构与你要求的完全一致
通用封装版本
可以封装为通用工具函数,支持自定义id字段、父级字段、根节点值、子节点字段名:
/** * 扁平对象数组转树形结构 * @param {Array} arr 原始扁平数组 * @param {String} idKey 唯一标识字段名,默认id * @param {String} parentKey 父级标识字段名,默认parentId * @param {Any} rootValue 根节点的父级值,默认0 * @param {String} childrenKey 子节点存储字段名,默认child * @returns {Array} 树形结构数组 */ function arrayToTree(arr, idKey = 'id', parentKey = 'parentId', rootValue = 0, childrenKey = 'child') { const map = {} const tree = [] arr.forEach(item => map[item[idKey]] = {...item, [childrenKey]: map[item[idKey]]?.[childrenKey] || [] }) arr.forEach(item => item[parentKey] === rootValue ? tree.push(map[item[idKey]]) : map[item[parentKey]]?.[childrenKey].push(map[item[idKey]])) return tree } // 调用示例 const treeMenu = arrayToTree(menus)
内容的提问来源于stack exchange,提问作者Bruno
相关产品推荐
相关产品推荐

