为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

婷婷同学_ 2022-09-16 21:15:54
我有将简单的对象数组转换为树数组的函数。该函数不是我编写的。该函数使用洛达什 _.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);这是输出:我不明白这段代码:..._.each(list, item => {    if (item.parentId) {      list[treeMap[item.parentId]].children.push(item);    } else {      treeArray.push(item);    }...我得到了一个由两个对象组成的树数组,但为什么有两个对象?我真的很累,很困惑。在这里,最初树阵列是空的。然后,仅当项目没有“父 Id”时,才会推送该项目。如果它有“父 Id” - 则应用适当的逻辑。并且每个子项被推送到相应的父项的“子项”属性。原始列表发生了变异。但是没有树的“气味”。复杂的父对象位于列表数组中。我说的对吗?但是为什么在最后,树阵列由两个对象组成,而不是一个对象?为什么复杂的父对象会神奇地出现在树数组中?
查看完整描述

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": []

  }

]


查看完整回答
反对 回复 2022-09-16
?
临摹微笑

TA贡献1982条经验 获得超2个赞

我得到了一个由两个对象组成的树数组,但为什么有两个对象?

您正确地标识了没有 的对象将追加到该数组中。输入数组中有两个这样的对象。由于它们没有对父级的引用,因此应将其视为顶级对象,这就是为什么您应该在 中看到两个对象的原因。所有其他输入对象都应该在某个属性的更深处找到。parentIdtreeArraychildren

如果它有“父 Id” - 则应用适当的逻辑。并且每个子项被推送到相应的父项的“子项”属性。原始列表发生了变异。但是没有树的“气味”。

实际上, 在代码的该部分中未被引用。但是我们迟早会有一个对象A,它的父对象(B)将是一个顶级对象,即没有父对象。treeArray

我们知道,当访问 B 时(可能在 A 之前或 A 之后,这并不重要),它将被添加到 。因此,尽管没有直接引用,但通过将A添加到B的属性中,我们会影响(或将要)推送的内容。treeArraytreeArraychildrentreeArray

换句话说:通过突变B的属性(当我们向它添加A时),我们也(间接地)突变(如果B已经在其中),或者将突变的B推入(如果B仍然被添加到其中)。childrentreeArraytreeArray

因此,通过将对象添加到其他对象的数组中,我们创建了一个嵌套对象的集合体。只有没有父级的对象才能保持“独立”:它们最终在 中,但与它们一起“拖动”一个包含所有嵌套对象的子树,这些子对象可能有自己的对象,...等。childrentreeArraychildrenchildren


查看完整回答
反对 回复 2022-09-16
  • 2 回答
  • 0 关注
  • 83 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信