简介
比方说,我们有一个树形数据结构。这可能是一个组织层次结构,项目分解,动物/植物分类学,等等。下面是一个树形结构的例子。
在一个应用程序中,以下面的格式存储这些信息是相当普遍的,特别是如果有一个1对多的父/子节点关系。
const data = [
{ id: 56, parentId: 62 },
{ id: 81, parentId: 80 },
{ id: 74, parentId: null },
{ id: 76, parentId: 80 },
{ id: 63, parentId: 62 },
{ id: 80, parentId: 86 },
{ id: 87, parentId: 86 },
{ id: 62, parentId: 74 },
{ id: 86, parentId: 74 },
];
那么,我们如何从这个对象数组的格式变成一个分层的树形格式?当你利用JavaScript对象引用的优势时,这实际上是一个相当容易的任务。它可以在没有递归的情况下,在O(n)时间内完成。
一些快速术语
为了确保我们说的是同一种语言,让我们快速浏览一下我可能使用的一些术语。我们数组中的每个元素(即我们树上的每个圆)是一个 "节点"。一个节点可以是多个节点的 "父",也可以是一个节点的 "子"。在上图中,node 86 是node 80 和node 87 的 "父"。node 86 是node 74 的 "子"。我们树的顶部节点是 "根"。
整体方法论
为了建立我们的树,我们要做的是。
- 遍历
data数组 - 找到当前元素的父元素
- 在父元素的对象中,添加一个对子元素的引用
- 如果一个元素没有父元素,我们知道它将是我们树的 "根 "元素。
我们必须认识到,引用将沿着对象树向下维护,这就是为什么我们可以在O(n)时间内完成这个任务的原因。
制作一个ID到数组的位置图
虽然这并不是完全必要的,但让我们先创建一个元素ID到数组索引的映射。这将有助于我们在适当的时候添加对一个元素的父代的引用。
const idMapping = data.reduce((acc, el, i) => {
acc[el.id] = i;
return acc;
}, {});
这个映射将产生如下结果。你很快就会明白为什么这样做是有帮助的。
{
56: 0,
62: 7,
63: 4,
74: 2,
76: 3,
80: 5,
81: 1,
86: 8,
87: 6,
};
创建树
我们已经准备好创建我们的树了!让我们在对象中进行迭代,并为每个项目的父代分配引用。注意我们使用idMapping 来帮助我们定位父项。
let root;
data.forEach((el) => {
// Handle the root element
if (el.parentId === null) {
root = el;
return;
}
// Use our mapping to locate the parent element in our data array
const parentEl = data[idMapping[el.parentId]];
// Add our current el to its parent's `children` array
parentEl.children = [...(parentEl.children || []), el];
});
然后......这就是了!我们可以console.log 我们的树root 来确认。
console.log(root);
{
id: 74,
parentId: null,
children: [
{
id: 62,
parentId: 74,
children: [{ id: 56, parentId: 62 }, { id: 63, parentId: 62 }],
},
{
id: 86,
parentId: 74,
children: [
{
id: 80,
parentId: 86,
children: [{ id: 81, parentId: 80 }, { id: 76, parentId: 80 }],
},
{ id: 87, parentId: 86 },
],
},
],
};
为什么这样做
理解为什么这样做的最好方法是记住,data 数组的每个元素都是对内存中一个对象的引用,我们的forEach 循环中的el 变量是对内存中一个对象的引用(data 数组元素所引用的内存中的相应对象),而parentEl 也是对内存中一个对象的引用(同样,data 数组中的一个对象被引用)。
如果内存中的一个对象有一个子对象的引用数组,这些子对象可以有它们自己的子对象引用数组的增长。由于这些都是通过引用完成的,所以当你修改它的一个子对象时,你不需要告诉父对象任何事情。
总结
对象引用是JavaScript中的一个基础概念,我相信它总是需要更多的学习和理解。真正理解这个概念既可以帮助避免棘手的bug,又可以为看似复杂的问题提供相对简单的解决方案。