使用for循环递归循环对象数组

时间:2018-12-06 04:05:42

标签: javascript vue.js vuex

TLDR; 如果我有一个包含对象的数组,而其中一些对象是包含更多对象的数组,我如何递归地遍历这些对象并返回其中一个嵌套值?在下面的代码中,using return仅解析第一个元素。如果我不使用return,那么该值会丢失,因为它在递归范围之一中被破坏了。

var currentNode = [{...}, {...}, { items: [{...}, {...}] }]    

// If it's not found, looping through the children (there should be more wrapper nodes in the children)
    for (let node of currentNode) {
      if (node.uuid === uuidToFind) {
        return node;
      }
      // THIS CAN'T LOOP
      // BUT there's a lexical scoping problem if I don't use `return`
      return searchTreeForNode(node, nodeUuidToFind);
    }
  }

详细摘要:

我目前正在处理当前以Vuex状态保存的树状结构。

在进入问题之前,我有两个问题:

  1. 有没有办法解决我遇到的词汇范围界定问题?
  2. 如果没有,我的替代建议解决方案听起来如何?

另外,为了快速参考,我有一个JSFiddle demo here的副本。

使用以下尝试的解决方案,我在递归函数(特别是最后一个for of循环)中无法进行词法作用域定义。

但是,让我从描述树开始。它有两种不同的节点。

看起来像这样的individual node

{
    name: `string`
    value: `string`
}

还有一个wrapper nodewrapper node中的子级可以是individual nodewrapper node

其结构如下:

{
    type: `string`,
    children: [
        {},
        {},
        ...
    ]
}

这些节点也可以嵌套无数次。

这是一个示例对象:

{
    type: `level 1`,
    children: [
        {
            type: `level 2`,
            children: [
                {
                    type: `level 3`,
                    children: []
                },
                {
                    name: `item 1`,
                    value: `value 1`
                },
                {
                    name: `item 2`,
                    value: `value 2`
                },
                ...
            ]
        },
        {
            name: `item 1`,
            value: `value 1`
        },
        {
            type: `level 2.1`,
            children: [
                {
                    name: `item 3`,
                    value: `value 3`
                }
                ...
            ]
        }
        ...
    ]
}

有了这棵树,我希望能够在树的任何位置添加individual nodeswrapper nodes,但是这样做很麻烦。

我最初的尝试是遍历每个节点并为其分配一个UUID。计划是遍历树,当我找到匹配的UUID时,可以根据需要进行操作。

这是该尝试的代码:

// This starts with the top-level wrapper node
function searchTreeForNode(currentNode, nodeUuidToFind) {
  if (currentNode.uuid === nodeUuidToFind) {
    return currentNode;
  }

  // If it's a wrapper node, parse the children
  if (currentNode.hasOwnProperty("type")) {
    return searchTreeForNode(currentNode.children, nodeUuidToFind);
  }

  // If it's the contents of a wrapper node, see if that node lives in them
  if (Array.isArray(currentNode)) {
    let resolvedUuids = [];
    for (let node of currentNode) {
      resolvedUuids.push(node.uuid);
    }

    // If found, return the node
    let uuidLocation = resolvedUuids.indexOf(nodeUuidToFind);
    if (uuidLocation !== -1) {
      return currentNode[uuidLocation];
    }

    // If it's not found, looping through the children (there should be more wrapper nodes in the children)
    for (let node of currentNode) {
      // THIS CAN'T LOOP
      // BUT there's a lexical scoping problem if I don't use `return`
      return searchTreeForNode(node, nodeUuidToFind);
    }
  }
}

是否可以使以上代码正常工作?具体来说是遍历包装节点的子节点吗?

如果没有,我现在的想法是要拥有三样东西,而不仅仅是让树处于状态。

  1. nodeTree-具有与原始数据相同形状的对象,但是每个节点只是一个UUID
  2. allRuleUuids-字符串数组。每个字符串是节点的UUID
  3. nodeDataByUuid-键入allRuleUuids数组中的UUID的对象。每个对象将包含每个节点的数据。

我需要支持的行为如下:

  • 在树的任何位置添加individual nodewrapper node
  • 从树的任何部分删除individual nodewrapper node(包括其所有子代)。

在此先感谢您的帮助!

4 个答案:

答案 0 :(得分:1)

您可以使用索引数组来表示节点的路径,例如:

[0, 1, 4]

好的,这是一个过于简化的实现。

function getNode(tree, path) {
    let current = tree;

    // find node
    for (let i = 0; i < path.length; i++) {
        current = current.children[path[i]];
    }

    return current;
}

function addNode(tree, path, node) {
    const index = path.pop();
    // ^ careful, this mutates the original array
    // you can clone it if you wish
    // or use string paths like `0/1/4`
    // and use path.split('/') to get the indexes

    const parent = getNode(tree, path);

    parent.children[index] = node;
}

function deleteNode(tree, path) {
    const index = path.pop();
    const parent = getNode(tree, path);

    delete parent.children[index];
}

// you can get nodes like this
console.log(getNode(tree, [1]));

// and add nodes like this
addNode(tree, [0, 3], { name: 'test', value: 'test' });

// you can get the root node like this
console.log(getNode(tree, []));

我没有处理给出错误路径的情况,即,假设路径[0, 1, 4]中的所有索引都引用包装节点,但最后一个索引除外。但是你明白了。

答案 1 :(得分:1)

我更改了您的代码。这样就可以了

https://jsfiddle.net/v2rkj376/1/

function searchTreeForNode(currentNode, nodeUuidToFind) {
  if (currentNode.uuid === nodeUuidToFind) {
    return currentNode.name || 'untitled ('+currentNode.uuid+')';
  }

  // If it's a wrapper node, parse the children
  if (currentNode.hasOwnProperty("type")) {
    for(node of currentNode.children){
      const foundNode = searchTreeForNode(node, nodeUuidToFind);
      if(foundNode)
        return foundNode;
    }
  }
}

更清洁的选择是将结构弄平,然后进行简单查找。

const flattenTree = node => 
    node.children 
        ? [node, ...node.children.flatMap(flattenTree)] 
        : [node];

然后发现变得简单:

flattenTree(sampleTree).find(node=>node.uuid === 'item5');

https://jsfiddle.net/3dzbto8n/

答案 2 :(得分:0)

我认为您需要这样的东西:

    currentNode.forEach( singleNode => {
      evalueateChildNodes(singleNode);
    });

 evaluateChildNodes(node) {

 // You can check the id here and assign it to some variable if necessary
 // Or if you found your specific node, you can push a new node to its children etc.
 // Note that even though the function name is 'evaluateChildNodes',
 // it will touch all root nodes due to the forEach() above.

    if (node.children) {
      evaluateChildNodes(node.children);
    }
  }

此代码将递归遍历整棵树并触摸每个节点。

或者,您可以将以上内容分配给变量,只要发现合适的内容就返回节点。

答案 3 :(得分:0)

我发现了我遇到的问题!我必须将值分配给一个临时变量,如果不是不确定的,则将其返回。

  if (Array.isArray(tree)) {
    for (let item of tree) {
      let tmp = searchTreeForNode(item, nodeUuid);
      if (tmp !== undefined) {
        return tmp;
      }
    }
  }