如何在嵌套对象中搜索字符串并更新所有父子节点的属性
如何给层级树节点设置matchFound属性(匹配自身/父/子节点时设为true)
我有一个层级结构的对象数组,现在需要实现:根据指定搜索字符串,更新每个节点的matchFound属性——只要节点自身、父节点或任意子节点包含该搜索字符串,就把matchFound设为true,否则设为false。之前已经实现了过滤数组的功能,但现在想直接修改原树的属性,而不是生成新的过滤数组。
原数据结构及旧实现代码:
const treeData = [{ name: 'Infiniti', matchFound: null, children: [{ name: 'G50', matchFound: null, children: [{ name: 'Pure AWD', matchFound: null, }, { name: 'Luxe', matchFound: null, }, ], }, { name: 'QX50', matchFound: null, children: [{ name: 'Pure AWD', matchFound: null, }, { name: 'Luxe', matchFound: null, }, ], }, ], }, { name: 'BMW', matchFound: null, children: [{ name: '2 Series', matchFound: null, children: [{ name: 'Coupé', matchFound: null, }, { name: 'Gran Coupé', matchFound: null, }, ], }, { name: '3 Series', matchFound: null, children: [{ name: 'Sedan', matchFound: null, }, { name: 'PHEV', matchFound: null, }, ], }, ], }, ]; let filteredData = []; function filter(searchString) { this.filteredData = this.search(this.treeData, searchString); console.log(this.filteredData) } function search(children, searchString) { return children.reduce((acc, item) => { if (item.name.toLowerCase().includes(searchString.toLowerCase())) { acc.push(item); } else if (item.children && item.children.length > 0) { const newItems = this.search(item.children, searchString); if (newItems.length > 0) { acc.push({ name: item.name, children: newItems }); } } return acc; }, []); } this.filter('infiniti'); this.filter('luxe');
解决方案
要实现直接更新节点matchFound的需求,需要分两次遍历树结构:
- 后序遍历:从叶子节点往上,先判断子节点是否匹配,再结合自身匹配状态,设置当前节点的初始
matchFound值 - 前序遍历:从根节点往下,把父节点的匹配状态传递给子节点——如果父节点是
true,子节点也必须设为true
完整实现代码:
const treeData = [{ name: 'Infiniti', matchFound: null, children: [{ name: 'G50', matchFound: null, children: [{ name: 'Pure AWD', matchFound: null, }, { name: 'Luxe', matchFound: null, }, ], }, { name: 'QX50', matchFound: null, children: [{ name: 'Pure AWD', matchFound: null, }, { name: 'Luxe', matchFound: null, }, ], }, ], }, { name: 'BMW', matchFound: null, children: [{ name: '2 Series', matchFound: null, children: [{ name: 'Coupé', matchFound: null, }, { name: 'Gran Coupé', matchFound: null, }, ], }, { name: '3 Series', matchFound: null, children: [{ name: 'Sedan', matchFound: null, }, { name: 'PHEV', matchFound: null, }, ], }, ], }, ]; // 后序遍历:先处理子节点,再设置当前节点的matchFound(自身匹配或子节点匹配) function updateMatchFromChildren(node, searchStr) { const lowerSearch = searchStr.toLowerCase(); let hasMatch = node.name.toLowerCase().includes(lowerSearch); if (node.children && node.children.length > 0) { // 遍历所有子节点,只要有一个子节点匹配,当前节点就标记为匹配 node.children.forEach(child => { if (updateMatchFromChildren(child, searchStr)) { hasMatch = true; } }); } node.matchFound = hasMatch; return hasMatch; } // 前序遍历:把父节点的匹配状态传递给子节点(父节点为true,子节点也设为true) function propagateMatchFromParent(node, parentMatch) { // 如果父节点匹配,当前节点也必须匹配 if (parentMatch) { node.matchFound = true; } if (node.children && node.children.length > 0) { node.children.forEach(child => { propagateMatchFromParent(child, node.matchFound); }); } } // 主函数:执行两次遍历更新matchFound function updateMatchFound(searchString) { // 重置所有matchFound为初始值null(避免上次搜索结果干扰) treeData.forEach(node => { function reset(node) { node.matchFound = null; if (node.children) node.children.forEach(reset); } reset(node); }); // 第一步:从子到根设置匹配状态 treeData.forEach(node => updateMatchFromChildren(node, searchString)); // 第二步:从根到子传递父节点的匹配状态 treeData.forEach(node => propagateMatchFromParent(node, false)); console.log('更新后的树结构:', treeData); } // 测试调用 updateMatchFound('infiniti'); // updateMatchFound('luxe');
代码说明
updateMatchFromChildren:递归遍历子节点,先处理所有子节点的匹配状态,再判断当前节点自身是否匹配。只要子节点中有一个匹配,当前节点的matchFound就设为true。propagateMatchFromParent:递归遍历子节点,如果父节点的matchFound是true,就把当前节点的matchFound强制设为true——这一步保证了父节点匹配时,所有子节点都会被标记为匹配。updateMatchFound:主函数,先重置所有节点的matchFound,然后执行两次遍历完成更新。
测试updateMatchFound('infiniti')时,Infiniti节点及其所有子节点的matchFound都会变成true;测试updateMatchFound('luxe')时,所有名为Luxe的节点、它们的父节点(G50、QX50)、以及根节点Infiniti的matchFound都会变成true,而BMW相关节点则为false。
内容的提问来源于stack exchange,提问作者Jason22
相关产品推荐
相关产品推荐

