用扁平数组实现多级菜单映射,通过idMap和childrenMap索引支持O(1)父子查询、O(k)祖先/后代遍历,可扩展path路径用于路由与权限控制。

用数组实现多级菜单映射,核心是把菜单结构建模为「父子关系的扁平化列表」,再通过遍历或索引快速定位任意节点的父、子、祖先、后代。不依赖树形递归或复杂对象嵌套,轻量、易维护、适合配置驱动场景。
一、用扁平数组描述多级菜单结构
每个菜单项用一个对象表示,必须包含唯一 id 和 parent_id(根节点 parent_id 为 null 或 0):
const menuList = [
{ id: 1, name: '首页', parent_id: null },
{ id: 2, name: '产品中心', parent_id: null },
{ id: 3, name: 'Web 应用', parent_id: 2 },
{ id: 4, name: '移动应用', parent_id: 2 },
{ id: 5, name: 'iOS 版', parent_id: 4 },
{ id: 6, name: 'Android 版', parent_id: 4 },
{ id: 7, name: '关于我们', parent_id: null },
{ id: 8, name: '团队介绍', parent_id: 7 },
{ id: 9, name: '加入我们', parent_id: 7 }
];
这种结构天然支持动态增删(只需改 parent_id),也方便后端统一返回、前端缓存复用。
二、构建父子关系索引,提升查找效率
遍历一次数组,生成两个关键映射表:
- idMap:id → 菜单项对象(O(1) 查单个节点)
- childrenMap:parent_id → 子节点数组(O(1) 查所有直接子项)
代码示例:
function buildMenuIndex(list) {
const idMap = {};
const childrenMap = {};
list.forEach(item => {
idMap[item.id] = item;
const pid = item.parent_id ?? 0;
if (!childrenMap[pid]) childrenMap[pid] = [];
childrenMap[pid].push(item);
});
return { idMap, childrenMap };
}
const { idMap, childrenMap } = buildMenuIndex(menuList);
三、实战:快速查任意变量的父子关系
基于上面的索引,以下查询都不需要递归,时间复杂度均为 O(1) 或 O(k)(k 为层级深度):
-
查父节点:直接读
idMap[item.parent_id] -
查所有直接子节点:读
childrenMap[item.id] || [] - 查所有祖先(向上链):循环取 parent_id,查 idMap,直到 parent_id 为空
- 查所有后代(向下展开):BFS 或 DFS 遍历 childrenMap,从目标 id 开始逐层展开
例如查 id=5(iOS 版)的完整路径:
function getAncestors(id, idMap, childrenMap) {
const path = [];
let current = idMap[id];
while (current && current.parent_id !== null) {
current = idMap[current.parent_id];
path.unshift(current);
}
return path;
}
// getAncestors(5, idMap, childrenMap)
// → [{id:2,name:'产品中心'}, {id:4,name:'移动应用'}]
四、扩展技巧:支持路径标识与权限校验
可在构建索引时预计算每个节点的 path 字符串(如 "2/4/5"),用于路由匹配或权限判断:
function buildMenuIndexWithPath(list) {
const idMap = {};
const childrenMap = {};
list.forEach(item => {
idMap[item.id] = { ...item };
});
// 第二遍补全 path 和 level
list.forEach(item => {
const parent = item.parent_id ? idMap[item.parent_id] : null;
const path = parent ? `${parent.path}/${item.id}` : String(item.id);
idMap[item.id].path = path;
idMap[item.id].level = parent ? parent.level + 1 : 0;
if (!childrenMap[item.parent_id]) childrenMap[item.parent_id] = [];
childrenMap[item.parent_id].push(idMap[item.id]);
});
return { idMap, childrenMap };
}
这样,idMap[5].path === "2/4/5",可用于前端路由守卫(如只允许访问 path 前缀为 "2" 的菜单)、或 RBAC 权限比对。








