如何在JavaScript中递归地构建菜单列表对象?

如何在JavaScript中递归地构建菜单列表对象?,javascript,arrays,ecmascript-6,javascript-objects,arrow-functions,Javascript,Arrays,Ecmascript 6,Javascript Objects,Arrow Functions,一系列 ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium']; 我想构造一个映射对象,它看起来像: { 'social': { swipes: { women: null, men: null } }, 'upgrade': { premium: null } } const-menu

一系列

['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
我想构造一个映射对象,它看起来像:

{
    'social': {
        swipes: {
            women: null,
            men: null
        }
    },
    'upgrade': {
        premium: null
    }
}
const-menu=['/social/swipes/women','/social/likes/men','/upgrade/premium'];
常量映射={};
常量addLabelToMap=(根,标签)=>{
如果(!map[root])map[root]={};
如果(!map[root][label])map[root][label]={};
}
const buildMenuMap=菜单=>{
菜单
//复制菜单
//.slice返回原始数组的副本
.slice()
//通过拆分/,将字符串转换为数组
//删除第一个,因为它是空的
//.map返回一个新数组
.map(item=>item.split('/').splice(1))
//遍历每个数组及其元素
.forEach((元素)=>{
设root=map[element[0]| |“”;
for(设i=1;i简单到:

 root = root[label];
如果将助手函数更改为:

 const addLabelToMap = (root, label) => {
    if(!root[label]) root[label] =  {};
 }

我会这样写:

 const buildMenuMap = menus => {
   const root = {};

   for(const menu of menus) {
     const keys = menu.split("/").slice(1);
     const prop = keys.pop();
     const obj = keys.reduce((curr, key) => curr[key] || (curr[key] = {}), root);
     obj[prop] = null;
  }

  return root;
}

使用
reduce
而不是
map
。在这种情况下,根将是累加器:

const buildMenuMap = menu =>
  menu.reduce((root, item) => {
    let parts = item.slice(1).split("/");
    let lastPart = parts.pop();
    let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
    leaf[lastPart] = null;
    return root;
  }, Object.create(null));
说明:

对于
菜单
数组中的每个
,我们首先去掉前面的
“/”
(使用
切片(1)
),然后通过
拆分
部分

然后,我们从这个结果数组中删除
lastPart
(最后一部分与其余部分分开处理)

对于
部分
数组中的每个剩余部分,我们遍历
数组。在每个遍历级别,我们要么返回该级别的对象
acc[part]
,如果它已经存在,,要么创建并返回一个新的对象,如果它不
(acc[part]={})

在到达最后一级
leaf
之后,我们使用
lastPart
将值设置为
null

请注意,我们将
Object.create(null)
传递给
reduce
Object.create(null)
创建一个无原型的对象,这样使用
root[someKey]
就更安全了,而无需检查
someKey
是否为自有属性

示例:

constbuildmenumap=menu=>
menu.reduce((根,项)=>{
让parts=item.slice(1).拆分(“/”);
让lastPart=parts.pop();
让leaf=parts.reduce((acc,part)=>acc[part]| |(acc[part]={}),root);
叶[lastPart]=null;
返回根;
},Object.create(null));
让arr=['/social/swipes/women','/social/swipes/men','/upgrade/premium'];
让结果=构建菜单映射(arr);

控制台日志(结果)我刚刚调试了你的代码,看看哪里出了问题,我敦促你也这样做。你犯了两个(明显的)错误:

首先,在第一次迭代中,
map
的值只是一个空对象
{}
root
的值被初始化为
标签
滑动

.forEach((element) => {
  let root = map[element[0]] || "";
  ...
  root = root[label];
}
因此,您得到
根[label]
未定义的
,因此新根是
未定义的

第二,您在任何地方都可以使用
map

const addLabelToMap = (root, label) => {
  if(!map[root]) map[root] = {};
  if(!map[root][label]) map[root][label] = {};
}
相反,您应该将其作为一个参数,以便能够执行递归

const addLabelToMap = (root, label) => {
  if(!root[label]) root[label] = {};
}

要调试代码,请在脚本标记中使用js创建一个简单的HTML文件,然后使用
python-mhttp.server
从本地计算机提供该文件。然后,您可以添加一个调试点,并一步一步地检查代码。

尝试将此作为一个整体解决方案:

const menu = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

const deepMerge = (target, source) => {
  // Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
  for (let key of Object.keys(source)) {
    if (source[key] instanceof Object && key in target) Object.assign(source[key], deepMerge(target[key], source[key]))
  }

  // Join `target` and modified `source`
  Object.assign(target || {}, source)
  return target
};

const buildMenuMap = menu => {
  return menu
    .map(item => item.split('/').splice(1))

    // The `root` value is the object that we will be merging all directories into
    .reduce((root, directory) => {

      // Iterates backwards through each directory array, stacking the previous accumulated object into the current one
      const branch = directory.slice().reverse().reduce((acc, cur) => { const obj = {}; obj[cur] = acc; return obj;},null);

      // Uses the `deepMerge()` method to stitch together the accumulated `root` object with the newly constructed `branch` object.
      return deepMerge(root, branch);
    }, {});
};

buildMenuMap(menu);

注意:深度合并解决方案取自

您可以使用、&

buildMenuMap

该函数将数组作为输入,并将其简化为一个对象,其中对于数组中的每个条目,使用
addLabelToMap
函数用相应的层次结构更新对象。每个条目都转换为一个级别数组(
c.substring(1).split(“/”

addLabelToMap

该函数接受2个输入

  • obj-当前根对象/节点
  • ar-子层次结构的数组
返回更新的对象

逻辑

  • 函数弹出第一个值(
    let key=ar.shift()
    )作为键,并在对象中添加/更新(
    obj[key]=obj[key]| |{};
  • 如果当前对象存在子层次结构(
    If(ar.length)
    ),则递归调用函数更新对象直到结束(
    addLabelToMap(obj[key],ar)
  • Else(无进一步的子层次结构),检查对象是否由于数组中的其他项而具有某种层次结构(
    Else,如果(!object.keys(obj[key]).length)
    )。如果没有层次结构,即它是一个叶,则将该值设置为
    null
    obj[key]=null
    注意,如果数组中永远不会有类似于
    /social/swipes/men/young
    的条目以及现有条目,则
    else if
    块可以简化为简单的
    else
  • 对象已更新,返回最终更新的对象
让arr=['/social/swipes/women','/social/swipes/men','/upgrade/premium'];
函数addLabelToMap(obj,ar){
让key=ar.shift();
obj[key]=obj[key]|{};
如果(ar.length)添加标签标记映射(obj[键],ar);
如果(!Object.keys(obj[key]).length)obj[key]=null,则为else;
返回obj;
}
函数构建菜单映射(ar){
返回ar.reduce((a,c)=>addLabelToMap(a,c.substring(1).split(“/”),{});
}

日志(buildMenuMap(arr))这个问题是关于创建对象并在通过
输入循环时保持其状态的问题