最近做一些树形的操作,发现有个轻量的库,就不用自己写了。使用后看下源码,先看下用法和整体策略。
1. 基础用法
const each = require('tree-interate')
const tree = []
const options = {
ignoreParentalsArray: false,
ignoreParents: false,
}
each(tree, options, function interate (node, parent, parents, parentalIndex) {
const parentNames = []
// 可以很方便的找到当前节点所有的父亲
parents.forEach(function(anscestor){
parentNames.push(anscestor.name)
})
console.log(
'Current Node :', node.name,
'Current Parent : ', parent.name,
'Parents : [ ', parentNames , ' ]'
)
})
2. 整体策略
- 创建一个内置的 Root 节点,这个主要用于适配要遍历的 tree 结构
const itOver = [
{
child: {
__root: true,
// tree 就是传入需要遍历的树
children: tree,
}
}
]
// 根据参数做一些根节点的初始化,所以全部属性如下
const itOver = [
{
child: {
__root: true,
// tree 就是传入需要遍历的树
children: tree,
// 父级节点,有多种判断,这里其实判断得很怪,感觉一定不会执行分支逻辑
parent: itOver,
// 以下 2 个属性根据配置决定是否需要,parents 会存所有路径的父节点数据,很占内存
parents: [],
// 这个属性感觉只是一个遍历的当前父的索引记录,在个别场景估计才有用
parentalIndex: [],
}
}
]
// 根节点 parent 的初始化逻辑
// 先定义 parent 就是自身
// { child, parent: { child } }
itOver[0].parent = itOver;
// 如果 parent 这个时候不存在(这个判断没有用,默认肯定有值),或者是数组为空
// itOver[0].parent.length -> 直接初始化的 itOver.length,肯定等于 1
// false || false -> false
if (!itOver[0].parent || itOver[0].parent.length === 0) {
// 就把当前 parent = { child }
itOver[0].parent = itOver[0];
}
// 如果没有 children 且 child 不为空,这里感觉也不存在,因为在最初的时候 !tree 直接 return 了代码执行
// itOver[0].parent.child -> 这个值是初始化直接给的,所以一定存在
// false && true -> false
if (!itOver[0].parent.children && itOver[0].parent.child) {
itOver[0].parent = itOver[0].parent.child;
}
创建此结构可以适配 2 种入参,可以很好的从上到下的遍历
// 第一种结构, 直接就是铺开的多根节点
const tree1 = [
{
children: [],
},
{
children: [],
},
]
// 第二种结构,常规的一个树对象
const tree2 = {
children: [],
}
- 对树进行遍历
这里使用的深度优先遍历,从根节点开始,遍历所有子节点,从左到右跟踪记录。退出条件有 2 个:itOver 数组为空,或者 val.child 为空。所以这里其实有漏洞,[undefined] 类型的子节点会影响到遍历是否继续。
let val
while (val = itOver.shift()) {
if (val.child) {
// 获取当前 Context 下的节点信息,给到回调遍历函数进行操作
// 判断是否是根节点
const parentIsRoot = val.parent && val.parent.__root
const callbackResponse =
// 根节点
interatorCallback(val.child, {}, [], val.parentalindex)
// 非根节点
interatorCallback(val.child, val.parent, val.parents, val.parentalIndex)
// 判断遍历是否有返回值,如果没有返回值,会继续遍历
// 这里有隐式判断,感觉可以直接判断 undefined and null 会更加合适一些
if (!callbackResponse && callbackResponse !== undefined) {
return tree
}
// 遍历树所有的子节点,从左到右
if (val.child.children) {
// parentalsIndex 主要记录当前遍历到的父级索引
let parentalIndex
// zi 表示当前节点子节点的索引
for (let zi = 0; zi < val.child.children.length; ++zi) {
parentalIndex = [zi]
for(let z = 0; z < val.parentalIndex.length; ++z){
parentalIndex.push(val.parentalIndex[z])
}
// 主要逻辑
itOver.push({
parent: val.child,
child: val.child.children[zi],
parents: [val.child].concat(val.parents),
parentalIndex: parentalIndex,
})
}
}
}
}
return tree