You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JS递归处理多维数组:筛选选中项及其父节点(栈溢出问题)

解决分类多维数组筛选选中项及父节点的栈溢出问题

我们需要处理一个嵌套的分类数组(每个节点包含childCategories子数组),核心需求是筛选出所有选中项及其所有父节点。当前实现的递归代码在单选场景下工作正常,但多选(比如同时选中节点C和D)时,会触发Maximum call stack size exceeded栈溢出错误。

问题原因分析

栈溢出通常源于递归逻辑的无限循环或调用栈深度超限,多选场景下的常见诱因:

  • 递归处理多个选中项时,重复对同一父节点发起递归调用,导致调用栈持续累积
  • 递归函数未设置有效终止条件,或终止条件在多选场景下失效
  • 未记录已处理节点,导致同一节点被反复触发递归

解决方案

方案1:迭代法(无栈溢出风险,推荐)

思路:先遍历整个分类树收集所有选中项的完整路径(从根到选中节点的父节点链),再基于这些路径构建最终的筛选树,确保每个节点仅被处理一次。

function filterSelectedCategoriesWithParents(categories, selectedIds) {
  // 收集所有选中项的完整路径
  const selectedPaths = [];
  const stack = [...categories.map(cat => ({ node: cat, path: [cat.id] }))];

  while (stack.length > 0) {
    const { node, path } = stack.pop();
    if (selectedIds.includes(node.id)) {
      selectedPaths.push([...path]);
    }
    if (node.childCategories?.length) {
      stack.push(...node.childCategories.map(child => ({
        node: child,
        path: [...path, child.id]
      })));
    }
  }

  // 构建筛选后的分类树
  const result = [];
  const nodeMap = new Map();

  // 存入所有涉及的节点
  function traverseAndAdd(node) {
    if (selectedPaths.some(path => path.includes(node.id))) {
      nodeMap.set(node.id, { ...node, childCategories: [] });
      node.childCategories?.forEach(traverseAndAdd);
    }
  }
  categories.forEach(traverseAndAdd);

  // 关联父子节点关系
  function buildTree(node) {
    const mappedNode = nodeMap.get(node.id);
    if (mappedNode) {
      const parentPath = selectedPaths.find(p => p.includes(node.id))?.slice(0, -1);
      if (parentPath?.length) {
        const parentNode = nodeMap.get(parentPath.at(-1));
        parentNode?.childCategories.push(mappedNode);
      } else {
        result.push(mappedNode);
      }
      node.childCategories?.forEach(buildTree);
    }
  }
  categories.forEach(buildTree);

  return result;
}

方案2:优化递归(加缓存避免重复调用)

思路:给递归函数添加processedIds集合,记录已处理的节点ID,避免对同一节点重复发起递归调用,从根源上防止栈溢出。

function filterSelectedCategoriesWithParents(categories, selectedIds, processedIds = new Set()) {
  const filtered = [];

  for (const category of categories) {
    if (processedIds.has(category.id)) continue;

    const hasSelectedChild = category.childCategories?.length 
      ? filterSelectedCategoriesWithParents(category.childCategories, selectedIds, processedIds).length > 0
      : false;

    if (selectedIds.includes(category.id) || hasSelectedChild) {
      processedIds.add(category.id);
      filtered.push({
        ...category,
        childCategories: filterSelectedCategoriesWithParents(category.childCategories, selectedIds, processedIds)
      });
    }
  }

  return filtered;
}
  • 方案1完全规避递归,适合层级极深的分类树场景
  • 方案2代码更简洁,通过缓存解决重复调用问题,适合层级适中的分类结构

内容的提问来源于stack exchange,提问作者Ben jamin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 23:30:02