背景
之前写了一篇的文章,有列表转树的需求自然就会有树转列表的需求,这里我把树转列表的思路与代码再整理一下。
思路分析
需求是什么?
老规矩,上图先说一下整体思路,就是遍历树中的每一个节点,在遍历过程中要把节点的父节点id记录下来,并作为该节点的parentId属性值(保留层级关系,后续根据这个parentId和节点的id可以转回树结构),然后把该节点存入一个列表中。遍历过程完成,也就实现了把树结构转换成了列表结构。树的遍历方式有两种,一种是深度优先遍历,一种是广度优先遍历,这两种方式思路如下图所示:
- 广度优先:
- 深度优先
思路看这两个图应该理得清楚了
我这里深度优先遍历采用了递归的方式,然后广度优先遍历采用了循环的方式。执行步骤
先说深度优先吧:
- 从第一层开始遍历,遍历该层中所有节点,为每一个遍历到的节点添加上parentId属性,存入结果列表。
- 每一个遍历节点存入结果列表后,检测该节点是否存在子节点,如果存在,则将子节点作为数据源重复第一步
- 当所有的节点都处理完成后,整个树结构也就完成了转化
再说说广度优先:
- 从第一层开始遍历,遍历该层中所有节点,为每一个遍历到的节点添加上parentId属性,存入结果列表。
- 每一个遍历节点存入结果列表后,检测该节点是否存在子节点,如果存在,则将子节点数据项push到第一层遍历的列表中(这里相当于在for循环的过程中,修改了被遍历的数组的内容,在循环过程中把它变长了)
- 当所有的节点都处理完成后,整个树结构也就完成了转化
代码
深度优先
/** 深度优先遍历树* 一个递归方法* @params tree:要转换的树结构数据* @params list:保存结果的列表结构数据,初始传[]* @params parentId:当前遍历节点的父级节点id,初始为null(因为根节点无parentId)* */function toListDF (tree, list, parentId = null) { for (let node of tree) { list.push({ id: node.id, name: node.name, children: [], parentId }) if (node.children.length !== 0) { toList(node.children, list, node.id) } }}
广度优先:
/** 广度优先遍历树* 一层循环* @params tree:要转换的树结构数据* @output list:返回转换好的列表结构数据* */function toListBF (tree) { const tempList = tree.slice(0) const res = [] for (let node of tempList) { // 向结果中push每一个节点的数据 res.push({ id: node.id, name: node.name, children: [], parentId: node.parentId === undefined ? null : node.parentId }) // 如果该节点还有子节点,那么将子节点追加到待循环列表的后面 if (node.children.length !== 0) { // 这里注意,push的是children里面的项 tempList.push(...node.children.map((item) => { // 这里给每一项加上parentId item.parentId = node.id return item })) } } return res}
这里给一个简单的树结构数据用来测试
const tree = [ { id: 0, name: 'root', children: [ { id: 1, name: 'child1', children: [ { id: 4, name: 'child1-1', children: [] }, { id: 5, name: 'child1-2', children: [] } ] }, { id: 2, name: 'child2', children: [ { id: 6, name: 'child2-1', children: [ { id: 8, name: 'child2-1-1', children: [] } ] } ] }, { id: 3, name: 'child3', children: [ { id: 7, name: 'child3-1', children: [] } ] } ] }]
上面代码直接扔到浏览器中即可运行,可自行看看结果。
总结
树转列表过程中,我这里的深度优先采用了递归方式,可能会对内存占用较多,使用时请自行权衡。
在广度优先的方式中,只用了一层循环,这里有一个核心的点,就是js在循环列表过程中,被循环的列表是可以修改的,比如push一个数据项,这样会让for循环多运行一次,这个点理解之后,我这里的广度优先遍历树的方法就好理解了。