本文介绍一种递归式、高性能的 javascript 方法,用于在嵌套的键值对树结构中按条件(如 label 字段)精准筛选节点,并保留从根到匹配项的完整路径及所有后代,适用于 label 搜索、多条件匹配等场景。
本文介绍一种递归式、高性能的 javascript 方法,用于在嵌套的键值对树结构中按条件(如 label 字段)精准筛选节点,并保留从根到匹配项的完整路径及所有后代,适用于 label 搜索、多条件匹配等场景。
在处理具有层级关系的配置型或菜单型数据时,我们常遇到以 ID 为键、对象为值的“命名树”(named tree)结构——即每个节点不是数组元素,而是通过字符串键(如 "28"、"482")索引的对象,且子节点统一挂载在 children 属性下。这种结构无法直接使用数组 filter() 或常见树遍历库,需定制化递归逻辑。
核心思路是:自顶向下深度优先搜索 + 自底向上路径重建。对每个节点:
- 若满足搜索条件(如 item.label === 'fish'),则完整保留该节点;
- 否则,递归检查其 children;若子树中存在匹配项,则仅保留当前节点并替换其 children 为过滤后的子树;
- 最终返回一个结构精简但路径完整的子树对象。
以下是实现代码(已修复原示例中的语法错误与逻辑细节):
/**
* 在命名树结构中按条件过滤节点,保留匹配项及其祖先路径和全部后代
* @param {Object} obj - 待搜索的树对象(键为 ID,值为节点)
* @param {Function} cb - 匹配回调函数,接收节点对象,返回布尔值
* @returns {Object|null} 过滤后的新树对象(可能为空对象或 null)
*/
const searchTree = (obj, cb) => {
if (!obj || typeof obj !== 'object') return null;
let result = null;
for (const key in obj) {
if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
const node = obj[key];
// 情况1:当前节点匹配 → 直接保留完整节点
if (cb(node)) {
(result ??= {})[key] = { ...node }; // 浅拷贝避免污染原数据
continue;
}
// 情况2:当前节点不匹配,但有 children 且子树中存在匹配项
if (node.children && typeof node.children === 'object') {
const matchedChildren = searchTree(node.children, cb);
if (matchedChildren) {
(result ??= {})[key] = {
...node,
children: matchedChildren
};
}
}
}
return result;
};
✅ 使用示例:
// 搜索 label === 'fish' const fishResult = searchTree(tree, node => node.label === 'fish'); console.log(JSON.stringify(fishResult, null, 2)); // 输出包含 "28" → "188" → "482" 的完整路径及 "482" 下全部 children // 搜索多个关键词(如 'dog' 或 'station') const multiResult = searchTree(tree, node => ['dog', 'station'].includes(node.label));
⚠️ 注意事项:
- 该函数不修改原始树,返回全新对象(使用展开语法浅拷贝);
- 支持空 label(如 "label": "")和缺失 children 属性的健壮处理;
- 若需深度克隆(如节点含嵌套引用),可将 { ...node } 替换为 structuredClone(node)(现代环境)或第三方深拷贝工具;
- 时间复杂度为 O(n),空间复杂度为 O(h)(h 为树最大深度),无冗余遍历;
- 不依赖外部库,兼容 ES2019+ 环境。
? 进阶建议:如需支持模糊搜索、大小写不敏感或字段路径配置(如 cb(node) => node.meta?.tags?.includes('urgent')),只需扩展回调函数逻辑,主体递归结构无需改动——这正是高内聚、低耦合设计的优势所在。










