2 回答
TA贡献2016条经验 获得超9个赞
这是一种生成树的非常简单的方法,但理想情况下,您需要了解JavaScript中的指针/引用才能了解它是如何工作的。(它确实创建了很多指针/引用,还有其他方法可以用更少的指针和更少的内存使用量来做到这一点。此外,这仅在对平面列表进行排序并且确实会改变原始列表时才有效。还有其他方法可以从平面数组生成树,使用递归等,并且不会改变列表)
重要的是,基元(数字、布尔值等)存储在堆栈上,对象(如数组、对象等)存储在堆上。对象的变量只是指向堆上对象位置的指针。我不确定推荐什么文章或YouTube视频,因为我阅读/观看了这么多不同的文章或视频 - https://www.google.com/search?q=stack+vs+heap
坚持你的例子,让我试着逐行解释(在代码中加上注释):
let a = [
{ id: 1, name: "ADMINISTRATION" },
{ id: 2, name: "ADMIN_GROUP", parentId: 1 },
{ id: 3, name: "ADMIN_GROUP_CREATE", parentId: 2 },
{ id:168, name: "HELP_EDIT" }
]
function makeTreeFromArray(list) {
const treeArray = [];
const treeMap = {};
//this loop generates the treeMap
//and adds a .children prop to each item in the list:
list.forEach((item, index) => {
treeMap[item.id] = index;
// list[index].children = [];
item.children = [];
});
//Output of that function:
//it maps ids to index in the list
/*
treeMap = {
"1": 0,
"2": 1,
"3": 2,
"168": 3
}
*/
//this loop pushes each child to its parent in the original list (if it has a parentId)
//and it does this using the treeMap (essentially a lookup of ids to indexes).
//if the child does not have a parent, then it is the highest level parent
//in this case, push it to our output treeArray (with all its children):
list.forEach(item => {
if (item.parentId) {
list[treeMap[item.parentId]].children.push(item);
} else {
treeArray.push(item);
}
});
//console.log('treeMap', treeMap);
//console.log('list', list);
return treeArray;
}
const newArr = makeTreeFromArray(a);
console.log(newArr);
它之所以有效,是因为如果创建指针/引用(原始指针的副本),则可以通过原始指针或新副本更新原始项目。下面是一个微不足道的例子:
了解如何使用任一变量更改原始数据(同样重要的是,我们正在使用对象,使用基元,这将是不一样的)。
(注意:JavaScript 是垃圾回收的,因此每当堆上指向内存的所有指针都超出范围时,就会清理数据。默认情况下,JavaScript使处理内存变得“简单”,但是随着您更深入地了解它正在做的事情,它变得更加复杂。像C++或Rust这样的低级语言让你自己管理内存 - 但Rust通过编译器帮助你做到这一点,C++你基本上是独立的,可能会导致问题)
//original is a pointer to a location on the heap, where the object lives:
const original = {id: 1, children: []};
//pointerToOriginal is just a copy of the original pointer,
//so now they both point to the same location on the heap:
const pointerToOriginal = original;
//I can use either pointer, to change the object on the heap:
pointerToOriginal.id +=1;
//and it can be seen with both pointers (as they point to the same object):
console.log(pointerToOriginal, original);
//I can use either pointer, to change the object on the heap:
original.id +=1;
//and it can be seen with both pointers (as they point to the same object):
console.log(pointerToOriginal, original);
//Finally, we can see that the objects are equal
//and by default JavaScript just compares the memory addresses of each pointer:
console.log(pointerToOriginal == original); //true
如果你想制作真正的副本,你需要这样做。对于只有基元数据的“平面”对象,您可以像这样执行此操作(使用散布运算符)。对于嵌套对象和/或以 Object 作为其值的对象,这不会完全开箱即用,因为它只会创建一个“浅层副本”,但我们可以创建一个深度复制函数(但这是一个很长的讨论):
//original is a pointer to a location on the heap, where the object lives:
const original = {id: 1, children: []};
//pointerToOriginal is now a new pointer to a shallow copy of the original object,
//so now they both point to DIFFERENT locations on the heap:
const pointerToNewCopy = {...original};
//Each pointer now only updates its Object on the heap:
pointerToNewCopy.id +=1;
//we see the orignal is un-changed:
console.log(pointerToNewCopy, original);
//Each pointer now only updates its Object on the heap:
original.id +=1;
//we see the pointerToNewCopy is un-changed:
console.log(pointerToNewCopy, original);
//Finally, we can see that the objects are not equal (even though all props are the same)
//this is because by default JavaScript just compares the memory addresses of each pointer:
console.log(pointerToNewCopy == original); //false
重要的是,我们使用上面的对象,使用基元,它将是不一样的:
//primitive example:
let original = 5;
let copy = original;
//only the copy changes:
copy +=1;
console.log(copy, original);
//only the original changes:
original +=1;
console.log(copy, original);
如果您从函数中 return 语句之前的最后一行(或者您可以在函数运行后向外调用它,因为它改变了原始列表),您将看到以下内容(在此代码段的控制台中,Google chrome 控制台不会通知您有指针/引用副本)。需要注意的重要事项是,id=1 的子级是该列表中原始项(ref4 和 ref6)的引用副本:console.log(list)makeTreeFromArray(list)
这就是树的构建方式,从这个突变的原始列表中。希望现在有意义吗?这是一个不平凡的话题。
list = [
{
"id": 1,
"name": "ADMINISTRATION",
"children": [
{
/**id:4**/
"id": 2,
"name": "ADMIN_GROUP",
"parentId": 1,
"children": [
{
/**id:6**/
"id": 3,
"name": "ADMIN_GROUP_CREATE",
"parentId": 2,
"children": []
}
]
}
]
},
/**ref:4**/,
/**ref:6**/,
{
"id": 168,
"name": "HELP_EDIT",
"children": []
}
]
TA贡献1982条经验 获得超2个赞
我得到了一个由两个对象组成的树数组,但为什么有两个对象?
您正确地标识了没有 的对象将追加到该数组中。输入数组中有两个这样的对象。由于它们没有对父级的引用,因此应将其视为顶级对象,这就是为什么您应该在 中看到两个对象的原因。所有其他输入对象都应该在某个属性的更深处找到。parentId
treeArray
children
如果它有“父 Id” - 则应用适当的逻辑。并且每个子项被推送到相应的父项的“子项”属性。原始列表发生了变异。但是没有树的“气味”。
实际上, 在代码的该部分中未被引用。但是我们迟早会有一个对象A,它的父对象(B)将是一个顶级对象,即没有父对象。treeArray
我们知道,当访问 B 时(可能在 A 之前或 A 之后,这并不重要),它将被添加到 。因此,尽管没有直接引用,但通过将A添加到B的属性中,我们会影响(或将要)推送的内容。treeArray
treeArray
children
treeArray
换句话说:通过突变B的属性(当我们向它添加A时),我们也(间接地)突变(如果B已经在其中),或者将突变的B推入(如果B仍然被添加到其中)。children
treeArray
treeArray
因此,通过将对象添加到其他对象的数组中,我们创建了一个嵌套对象的集合体。只有没有父级的对象才能保持“独立”:它们最终在 中,但与它们一起“拖动”一个包含所有嵌套对象的子树,这些子对象可能有自己的对象,...等。children
treeArray
children
children
添加回答
举报