首页 > 解决方案 > 如何理解在javascript反应中将数组转换为treeArray的函数代码片段?

问题描述

我有将简单的对象数组转换为 treeArray 的函数。函数不是我写的。该函数使用 lodash _.each。和原生 forEach 一样

这里的代码和示例数据:

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);
    }
...

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

标签: javascriptarraysreactjsobjecttree

解决方案


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

如果你想制作真正的副本,你需要这样做。对于只有原始数据的“平面”对象,您可以这样做(使用扩展运算符)。对于嵌套对象和/或将对象作为其值的对象,这不会完美地开箱即用,因为它只创建一个“浅拷贝”,但我们可以创建一个深拷贝函数(但这是一个很长的讨论):

//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);

如果您console.log(list)从函数中 return 语句之前的最后一行开始makeTreeFromArray(list)(或者您可以在函数运行后在外部调用它,因为它改变了原始列表),您将看到以下内容(在此代码段的控制台中,google chrome 控制台不会通知您有指针/引用副本)。需要注意的重要一点是 id=1 的子项是该列表(ref4 和 ref6)中原始项目的参考副本:

这就是从这个变异的原始列表中构建树的方式。希望现在有意义吗?这是一个不平凡的话题。

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

推荐阅读