如何理解在javascript反应中将数组转换为树数组的函数代码片段?

我有将简单的对象数组转换为树数组的函数。该函数不是我编写的。该函数使用洛达什 _.each。它与本机相同


下面是代码和示例数据:


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 = {};


  _.each(list, (item, index) => {

    treeMap[item.id] = index;

    // list[index].children = [];

    item.children = [];

  });


  _.each(list, 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);

这是输出:

http://img4.mukewang.com/6324771f0001df9c05440592.jpg

我不明白这段代码:


...

_.each(list, item => {

    if (item.parentId) {

      list[treeMap[item.parentId]].children.push(item);

    } else {

      treeArray.push(item);

    }

...

我得到了一个由两个对象组成的树数组,但为什么有两个对象?我真的很累,很困惑。在这里,最初树阵列是空的。然后,仅当项目没有“父 Id”时,才会推送该项目。如果它有“父 Id” - 则应用适当的逻辑。并且每个子项被推送到相应的父项的“子项”属性。原始列表发生了变异。但是没有树的“气味”。复杂的父对象位于列表数组中。我说的对吗?但是为什么在最后,树阵列由两个对象组成,而不是一个对象?为什么复杂的父对象会神奇地出现在树数组中?


婷婷同学_
浏览 137回答 2
2回答

慕沐林林

这是一种生成树的非常简单的方法,但理想情况下,您需要了解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": []  }]

临摹微笑

我得到了一个由两个对象组成的树数组,但为什么有两个对象?您正确地标识了没有 的对象将追加到该数组中。输入数组中有两个这样的对象。由于它们没有对父级的引用,因此应将其视为顶级对象,这就是为什么您应该在 中看到两个对象的原因。所有其他输入对象都应该在某个属性的更深处找到。parentIdtreeArraychildren如果它有“父 Id” - 则应用适当的逻辑。并且每个子项被推送到相应的父项的“子项”属性。原始列表发生了变异。但是没有树的“气味”。实际上, 在代码的该部分中未被引用。但是我们迟早会有一个对象A,它的父对象(B)将是一个顶级对象,即没有父对象。treeArray我们知道,当访问 B 时(可能在 A 之前或 A 之后,这并不重要),它将被添加到 。因此,尽管没有直接引用,但通过将A添加到B的属性中,我们会影响(或将要)推送的内容。treeArraytreeArraychildrentreeArray换句话说:通过突变B的属性(当我们向它添加A时),我们也(间接地)突变(如果B已经在其中),或者将突变的B推入(如果B仍然被添加到其中)。childrentreeArraytreeArray因此,通过将对象添加到其他对象的数组中,我们创建了一个嵌套对象的集合体。只有没有父级的对象才能保持“独立”:它们最终在 中,但与它们一起“拖动”一个包含所有嵌套对象的子树,这些子对象可能有自己的对象,...等。childrentreeArraychildrenchildren
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

JavaScript