2 回答

TA貢獻2016條經(jīng)驗 獲得超9個贊
這是一種生成樹的非常簡單的方法,但理想情況下,您需要了解JavaScript中的指針/引用才能了解它是如何工作的。(它確實創(chuàng)建了很多指針/引用,還有其他方法可以用更少的指針和更少的內(nèi)存使用量來做到這一點。此外,這僅在對平面列表進行排序并且確實會改變原始列表時才有效。還有其他方法可以從平面數(shù)組生成樹,使用遞歸等,并且不會改變列表)
重要的是,基元(數(shù)字、布爾值等)存儲在堆棧上,對象(如數(shù)組、對象等)存儲在堆上。對象的變量只是指向堆上對象位置的指針。我不確定推薦什么文章或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);
它之所以有效,是因為如果創(chuàng)建指針/引用(原始指針的副本),則可以通過原始指針或新副本更新原始項目。下面是一個微不足道的例子:
了解如何使用任一變量更改原始數(shù)據(jù)(同樣重要的是,我們正在使用對象,使用基元,這將是不一樣的)。
(注意:JavaScript 是垃圾回收的,因此每當堆上指向內(nèi)存的所有指針都超出范圍時,就會清理數(shù)據(jù)。默認情況下,JavaScript使處理內(nèi)存變得“簡單”,但是隨著您更深入地了解它正在做的事情,它變得更加復雜。像C++或Rust這樣的低級語言讓你自己管理內(nèi)存 - 但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
如果你想制作真正的副本,你需要這樣做。對于只有基元數(shù)據(jù)的“平面”對象,您可以像這樣執(zhí)行此操作(使用散布運算符)。對于嵌套對象和/或以 Object 作為其值的對象,這不會完全開箱即用,因為它只會創(chuàng)建一個“淺層副本”,但我們可以創(chuàng)建一個深度復制函數(shù)(但這是一個很長的討論):
//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);
如果您從函數(shù)中 return 語句之前的最后一行(或者您可以在函數(shù)運行后向外調(diào)用它,因為它改變了原始列表),您將看到以下內(nèi)容(在此代碼段的控制臺中,Google chrome 控制臺不會通知您有指針/引用副本)。需要注意的重要事項是,id=1 的子級是該列表中原始項(ref4 和 ref6)的引用副本:console.log(list)makeTreeFromArray(list)
這就是樹的構建方式,從這個突變的原始列表中。希望現(xiàn)在有意義嗎?這是一個不平凡的話題。
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條經(jīng)驗 獲得超2個贊
我得到了一個由兩個對象組成的樹數(shù)組,但為什么有兩個對象?
您正確地標識了沒有 的對象將追加到該數(shù)組中。輸入數(shù)組中有兩個這樣的對象。由于它們沒有對父級的引用,因此應將其視為頂級對象,這就是為什么您應該在 中看到兩個對象的原因。所有其他輸入對象都應該在某個屬性的更深處找到。parentId
treeArray
children
如果它有“父 Id” - 則應用適當?shù)倪壿?。并且每個子項被推送到相應的父項的“子項”屬性。原始列表發(fā)生了變異。但是沒有樹的“氣味”。
實際上, 在代碼的該部分中未被引用。但是我們遲早會有一個對象A,它的父對象(B)將是一個頂級對象,即沒有父對象。treeArray
我們知道,當訪問 B 時(可能在 A 之前或 A 之后,這并不重要),它將被添加到 。因此,盡管沒有直接引用,但通過將A添加到B的屬性中,我們會影響(或?qū)⒁┩扑偷膬?nèi)容。treeArray
treeArray
children
treeArray
換句話說:通過突變B的屬性(當我們向它添加A時),我們也(間接地)突變(如果B已經(jīng)在其中),或者將突變的B推入(如果B仍然被添加到其中)。children
treeArray
treeArray
因此,通過將對象添加到其他對象的數(shù)組中,我們創(chuàng)建了一個嵌套對象的集合體。只有沒有父級的對象才能保持“獨立”:它們最終在 中,但與它們一起“拖動”一個包含所有嵌套對象的子樹,這些子對象可能有自己的對象,...等。children
treeArray
children
children
添加回答
舉報