如何遍历未知深度树状嵌套结构 按路径查找目标数组项
实现思路
要实现任意长度路径的类文件树遍历,同时支持节点弹出、移动这类修改操作,不能只返回路径匹配到的目标节点——必须在逐层往下匹配的过程中,同步记录目标节点的直接父节点、节点在父节点folders数组中的索引、父节点folders数组的直接引用,否则后续修改时找不到节点的上级位置,操作没法落地。
具体实现
首先写通用的路径查找函数,兼容任意层级嵌套、任意长度的路径输入:
// 示例根文件树,就是你给出的结构 const rootTree = [{ "name": "Audiograms", "folders": [{ "name": "2022" }, { "name": "2021" }, { "name": "2020" }] }, { "name": "Patient Paperwork" }, { "name": "Repairs" }] /** * 按路径查找文件树节点 * @param {Array} rootTree 根层级的文件/文件夹数组 * @param {Array<string>} path 路径数组,比如["Audiograms", "2022"] * @returns {Object} 查找结果 */ function findNodeByPath(rootTree, path) { let currentLevel = rootTree; let parentNode = null; let targetIndex = -1; // 路径为空直接返回失败 if (!path.length) return { success: false, node: null, parentLevel: null, index: -1 } for (let i = 0; i < path.length; i++) { const currentName = path[i]; // 在当前层级找名称匹配的项 targetIndex = currentLevel.findIndex(item => item.name === currentName); if (targetIndex === -1) { return { success: false, node: null, parentLevel: null, index: -1 } } const currentNode = currentLevel[targetIndex]; // 走到路径最后一位,就是目标节点 if (i === path.length - 1) { return { success: true, node: currentNode, parentNode: parentNode, parentLevel: currentLevel, index: targetIndex } } // 没到路径末尾,往下一层走,当前节点没有子文件夹就说明路径无效 if (!currentNode.folders || !Array.isArray(currentNode.folders)) { return { success: false, node: null, parentLevel: null, index: -1 } } parentNode = currentNode; currentLevel = currentNode.folders; } }
弹出(删除)匹配节点
查找成功后,直接利用返回的父级数组引用和索引,用splice删除即可,被删除的节点可以直接拿到用于后续操作:
const targetPath = ["Audiograms", "2022"]; const findResult = findNodeByPath(rootTree, targetPath); if (findResult.success) { // 删除对应索引的项,拿到被弹出的节点 const [removedNode] = findResult.parentLevel.splice(findResult.index, 1); console.log('弹出的节点:', removedNode); }
移动节点到其他位置
移动逻辑本质是「原位置弹出节点 + 节点插入到目标父文件夹的folders数组」,注意提前做循环引用校验,避免把文件夹移动到自身子目录下造成死循环:
/** * 移动节点到目标路径 * @param {Array} rootTree 根文件树 * @param {Array} sourcePath 原节点路径 * @param {Array} targetParentPath 目标父文件夹路径 */ function moveNode(rootTree, sourcePath, targetParentPath) { // 校验源节点是否存在 const sourceResult = findNodeByPath(rootTree, sourcePath); if (!sourceResult.success) throw new Error('源路径不存在'); // 校验目标父文件夹是否存在 const targetResult = findNodeByPath(rootTree, targetParentPath); if (!targetResult.success) throw new Error('目标路径不存在'); const sourceNode = sourceResult.node; // 简单循环校验:不能把节点移动到自身目录下 if (sourceNode === targetResult.node) throw new Error('不能将节点移动到自身目录下'); // 从原位置删除节点 const [movedNode] = sourceResult.parentLevel.splice(sourceResult.index, 1); // 目标文件夹如果没有子文件夹列表就初始化 if (!targetResult.node.folders) targetResult.node.folders = []; // 把节点加到目标文件夹下 targetResult.node.folders.push(movedNode); } // 调用示例:把Audiograms/2022移动到Repairs文件夹下 moveNode(rootTree, ["Audiograms", "2022"], ["Repairs"])
注意事项
- 如果业务中允许不同文件夹下存在同名文件夹/文件,不要用
name做匹配键,给每个节点加全局唯一的ID,路径数组存ID序列,匹配准确度会高很多 - 移动节点一定要做循环引用校验,绝对不能把一个文件夹移动到它自己、或者它自己的子文件夹下面,不然文件树会出现循环嵌套,后续遍历直接栈溢出
- 如果需要支持移动节点到根目录,补全查找函数对空路径的处理即可:空路径对应的父级数组就是根目录数组,没有父节点
- 如果要把节点插到目标文件夹的指定位置,而不是默认加到列表末尾,移动时多传一个目标位置索引,用
splice(insertIndex, 0, 待移动节点)就能完成指定位置插入
内容的提问来源于stack exchange,提问作者Mathew Crogan
相关产品推荐
相关产品推荐

