# js-tree-toolkit **Repository Path**: platform-plus/js-tree-toolkit ## Basic Information - **Project Name**: js-tree-toolkit - **Description**: js树处理工具类,支持常用遍历,查找,过滤,数组和tree相互转换等方法 - **Primary Language**: JavaScript - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 0 - **Created**: 2023-04-10 - **Last Updated**: 2024-07-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #### 数组转树 ```javascript function arrayToTree(array, parentIdKey = 'parentId', childKey = 'children') { const tree = []; const map = {}; array.forEach(item => map[item.id] = item); array.forEach(item => { const parent = map[item[parentIdKey]]; if (parent) { (parent[childKey] || (parent[childKey] = [])).push(item); } else { tree.push(item); } }); return tree; } ``` > 测试代码 ```javascript const data = [ { id: 1, pId: null, name: 'A' }, { id: 2, pId: 1, name: 'B' }, { id: 3, pId: 1, name: 'C' }, { id: 4, pId: 2, name: 'D' }, { id: 5, pId: null, name: 'E' } ]; const tree = arrayToTree(data, 'pId', 'subItems'); ``` #### 树转数组 ```javascript function treeToArray(tree, parentIdKey = 'parentId', childKey = 'children') { const array = []; const stack = [...tree]; while (stack.length > 0) { const node = stack.pop(); const item = { ...node }; delete item[childKey]; array.push(item); if (node[childKey]) { node[childKey].forEach(child => { child[parentIdKey] = node.id; stack.push(child); }); } } return array; } ``` > 测试代码 ``` const tree = [ { id: 1, name: 'A', children: [ { id: 2, name: 'B', children: [ { id: 4, name: 'D' } ] }, { id: 3, name: 'C' } ] }, { id: 5, name: 'E' } ]; const array = treeToArray(tree); console.log(array); ``` #### 树遍历的方法 ``` function traverseTree(tree, callback, childKey = 'children') { if (!Array.isArray(tree)) { return 'Invalid tree data: tree must be an array'; } if (typeof callback !== 'function') { return 'Invalid callback: callback must be a function'; } for (const node of tree) { callback(node); if (node[childKey]) { if (!Array.isArray(node[childKey])) { return `Invalid tree data: ${childKey} must be an array`; } const result = traverseTree(node[childKey], callback, childKey); if (result) { return result; } } } } ``` > 测试代码 ``` const tree = [ { id: 1, name: 'A', children: [ { id: 2, name: 'B', children: [ { id: 4, name: 'D' } ] }, { id: 3, name: 'C' } ] }, { id: 5, name: 'E' } ]; const result = traverseTree(tree, node => console.log(node.name)); if (result) { console.error(result); } ``` #### js tree修复方法 ``` function fixTree(tree, childKey = 'children') { if (!Array.isArray(tree)) { return; } tree.forEach(node => { if (!Array.isArray(node[childKey])) { node[childKey] = []; } fixTree(node[childKey], childKey); }); } ``` > 测试代码 ``` const tree = [ { id: 1, name: 'A', children: [ { id: 2, name: 'B', children: [ { id: 4, name: 'D' } ] }, { id: 3, name: 'C' } ] }, { id: 5, name: 'E', children: 'invalid' } ]; fixTree(tree); console.log(tree); ``` #### 获取树最大深度方法 ``` function getMaxDepth(tree, childKey = 'children') { if (!Array.isArray(tree) || tree.length === 0) { return 0; } let maxDepth = 0; tree.forEach(node => { maxDepth = Math.max(maxDepth, getMaxDepth(node[childKey], childKey)); }); return maxDepth + 1; } ``` > 测试代码 ``` const tree = [ { id: 1, name: 'A', children: [ { id: 2, name: 'B', children: [ { id: 4, name: 'D' } ] }, { id: 3, name: 'C' } ] }, { id: 5, name: 'E' } ]; const maxDepth = getMaxDepth(tree); console.log(maxDepth); ``` #### 获取根节点到当前节点数据的方法 ``` function getSubTree(tree, findNodeFunc, childKey = 'children', includeCurrentNode = true) { let subTree = null; for (let i = 0; i < tree.length; i++) { if (findNodeFunc(tree[i])) { return includeCurrentNode ? tree[i] : null; } if (tree[i][childKey]) { subTree = getSubTree(tree[i][childKey], findNodeFunc, childKey, includeCurrentNode); if (subTree) { return { ...tree[i], [childKey]:[subTree] }; } } } return subTree; } ``` > 测试数据 ``` const tree = [ { id: 1, children: [ { id: 2, children: [ { id: 4 }, { id: 5 } ] }, { id: 3 } ] } ]; const subTree = getSubTree(tree, node => node.id === 5); console.log(subTree); /* { id: 1, children: [ { id: 2, children: [ { id: 5 } ] } ] } */ ``` #### 获取兄弟节点方法 ``` function getSiblingNodes(tree, nodeFunc, childKey = 'children', includeCurrentNode = false) { let siblings = []; let parent = null; let currentNode = null; let findParentAndCurrentNode = (tree) => { if (Array.isArray(tree)) { for (let i = 0; i < tree.length; i++) { if (nodeFunc(tree[i])) { parent = tree; currentNode = tree[i]; return; } else { findParentAndCurrentNode(tree[i]); } } } else if (tree[childKey]) { for (let i = 0; i < tree[childKey].length; i++) { if (nodeFunc(tree[childKey][i])) { parent = tree; currentNode = tree[childKey][i]; return; } else { findParentAndCurrentNode(tree[childKey][i]); } } } } findParentAndCurrentNode(tree); if (parent) { siblings = parent[childKey].filter(n => includeCurrentNode || n !== currentNode); } return siblings; } ``` > 测试方法 ``` let tree = { name: 'root', children: [ { name: 'a', children: [ { name: 'a1' }, { name: 'a2' } ] }, { name: 'b', children: [ { name: 'b1' }, { name: 'b2' } ] } ] }; let nodeFunc = (node) => node.name === 'a1'; let siblings = getSiblingNodes(tree, nodeFunc); console.log(siblings); // 输出:[ { name: 'a2' } ] ``` #### 获取子节点方法 ``` function getChildren(node, filterFn, childKey = 'children', includeCurrent = true) { let result = []; if (includeCurrent && filterFn(node)) { result.push(node); } if (node[childKey]) { node[childKey].forEach(child => { result = result.concat(getChildren(child, filterFn, childKey, includeCurrent)); }); } return result; } ``` > 测试代码 ``` const tree = { id: 1, children: [ { id: 2, children: [ { id: 3 }, { id: 4 } ] }, { id: 5, children: [ { id: 6 } ] } ] }; console.log(getChildren(tree, node => node.id == 2)); ``` #### 获取叶子节点方法 ``` function getLeafNodes(node, childKey = 'children') { let result = []; if (!node[childKey] || node[childKey].length === 0) { result.push(node); } else { node[childKey].forEach(child => { result = result.concat(getLeafNodes(child, childKey)); }); } return result; } ``` > 测试代码 ``` const tree = { id: 1, subNodes: [ { id: 2, subNodes: [ { id: 3 }, { id: 4 } ] }, { id: 5, subNodes: [ { id: 6 } ] } ] }; console.log(getLeafNodes(tree, 'subNodes')); ``` #### 遍历树叶子节点 ``` function traverseLeafNodes(node, callback, childKey = 'children') { if (!node[childKey] || node[childKey].length === 0) { callback(node); } else { node[childKey].forEach(child => { traverseLeafNodes(child, callback, childKey); }); } } ``` > 测试代码 ``` const tree = { id: 1, subNodes: [ { id: 2, subNodes: [ { id: 3 }, { id: 4 } ] }, { id: 5, subNodes: [ { id: 6 } ] } ] }; traverseLeafNodes(tree, node => console.log(node.id), 'subNodes'); ``` #### 设置树的层级和是否叶子节点 ``` function setLeafNodesAndLevel(nodeOrNodes, level = 1, childKey = 'children') { if (Array.isArray(nodeOrNodes)) { // 如果参数是数组,则处理每个节点 nodeOrNodes.forEach(node => { node.level = level; if (!node[childKey] || node[childKey].length === 0) { node.isLeaf = true; } else { node.isLeaf = false; setLeafNodesAndLevel(node[childKey], level + 1, childKey); } }); } else if (typeof nodeOrNodes === 'object') { // 如果参数是单个对象,则处理该对象 nodeOrNodes.level = level; if (!nodeOrNodes[childKey] || nodeOrNodes[childKey].length === 0) { nodeOrNodes.isLeaf = true; } else { nodeOrNodes.isLeaf = false; setLeafNodesAndLevel(nodeOrNodes[childKey], level + 1, childKey); } } else { throw new Error('Invalid input. Expected an object or an array.'); } } ``` > 测试代码 ``` //入参为对象 const tree = { id: 1, subNodes: [ { id: 2, subNodes: [ { id: 3 }, { id: 4 } ] }, { id: 5, subNodes: [ { id: 6 } ] } ] }; setLeafNodesAndLevel(tree, 0, 'subNodes'); console.log(tree); //入参是数组 const inputArray = [ { name: 'Node 1', children: [ { name: 'Node 1.1', children: [ { name: 'Node 1.1.1', children: [] }, { name: 'Node 1.1.2', children: [] } ] }, { name: 'Node 1.2', children: [] } ] }, { name: 'Node 2', children: [ { name: 'Node 2.1', children: [] }, { name: 'Node 2.2', children: [] } ] } ]; // 执行函数 setLeafNodesAndLevel(inputArray); ``` #### 获取指定层级父节点的方法 默认获取父亲 ```vue function getParentNode(tree, filterFn, childKey = 'children') { if (!tree || typeof tree !== 'object') { return null; } function searchParentNode(node, parent) { if (filterFn(node)) { return parent; } if (Array.isArray(node[childKey])) { for (const child of node[childKey]) { const result = searchParentNode(child, node); if (result) { return result; } } } return null; } return searchParentNode(tree, null); } ``` > 测试代码 ```javascript // 示例用法: const tree = { name: 'A', children: [ { name: 'B', children: [ { name: 'C', children: [ { name: 'D', children: [ { name: 'E', }, ], }, ], }, ], }, ], }; const filterFn = (node) => node.name === 'C'; const parentNode = getParentNode(tree, filterFn); console.log(parentNode); // 输出: { name: 'B', children: [...] } ``` #### 计算叶子节点的个数 ```vue function countLeafNodes(node, childKey = 'children') { if (!node[childKey] || node[childKey].length === 0) { return 1; } else { let count = 0; node[childKey].forEach(child => { count += countLeafNodes(child, childKey); }); return count; } } ``` > 测试代码 ```javascript const tree = { id: 1, subNodes: [ { id: 2, subNodes: [ { id: 3 }, { id: 4 } ] }, { id: 5, subNodes: [ { id: 6 } ] } ] }; console.log(countLeafNodes(tree, 'subNodes')); // 输出: 3 ``` #### 把js tree的层级补充到指定层级 ```javascript function fillTree(tree, depth, fillFunction, childKey = 'children') { if (Array.isArray(tree)) { tree.forEach(item => fillTree(item, depth, fillFunction, childKey)); } else { if (depth < 1) return; if (!tree[childKey]) tree[childKey] = []; if (tree[childKey].length === 0) tree[childKey].push(fillFunction()); tree[childKey].forEach(child => fillTree(child, depth - 1, fillFunction, childKey)); } } ``` > 测试代码 ```javascript let tree = { value: 1, children: [ { value: 2, children: [ { value: 3 } ] }, { value: 4 } ] }; fillTree(tree, 3, () => ({value: 0})); console.log(JSON.stringify(tree, null, 2)); ``` #### 获取数组中对应的子节点 ```javascript function getArrayChildren(data, filterFn = () => true, includeCurrent = true, pIdKey = 'pId', idKey = 'id') { let result = []; let current = data.find(filterFn); if (current && includeCurrent) result.push(current); const getChildrenRecursively = (parentId) => { data.forEach(item => { if (item[pIdKey] === parentId && filterFn(item)) { result.push(item); getChildrenRecursively(item[idKey]); } }); }; if (current) getChildrenRecursively(current[idKey]); return result; } ``` > 测试代码 ```javascript const data = [ { id: 1, pId: null, name: 'Node 1' }, { id: 2, pId: 1, name: 'Node 1.1' }, { id: 3, pId: 1, name: 'Node 1.2' }, { id: 4, pId: 2, name: 'Node 1.1.1' }, { id: 5, pId: 2, name: 'Node 1.1.2' }, { id: 6, pId: null, name: 'Node 2' }, { id: 7, pId: 6, name: 'Node 2.1' }, { id: 8, pId: 6, name: 'Node 2.2' }, ]; const result = getArrayChildren(data, item => item.name.includes('Node 1'), true, 'pId', 'id'); console.log(result); ``` #### 获取数组中父节点的方法 ```javascript function getArrayParent(data, filterFn = () => true, includeCurrent = true, pIdKey = 'parentId', idKey = 'id') { let result = []; let current = data.find(filterFn); if (current && includeCurrent) result.push(current); while (current && current[pIdKey] !== null) { current = data.find(item => item[idKey] === current[pIdKey]); if (current) { result.push(current); } } return result; } ``` > 测试代码 ```javascript const data = [ { id: 1, parentId: null, name: 'Node 1' }, { id: 2, parentId: 1, name: 'Node 1.1' }, { id: 3, parentId: 1, name: 'Node 1.2' }, { id: 4, parentId: 2, name: 'Node 1.1.1' }, { id: 5, parentId: 2, name: 'Node 1.1.2' }, { id: 6, parentId: null, name: 'Node 2' }, { id: 7, parentId: 6, name: 'Node 2.1' }, { id: 8, parentId: 6, name: 'Node 2.2' }, ]; const result = getArrayParent(data, item => item.name.includes('Node 1'), true, 'parentId', 'id'); console.log(result); ``` #### 树节点移动方法 ```javascript function moveTreeNode(treeData, sourceNodeFilterFn, targetNodeFilterFn, childKey = 'children') { let sourceNode = null; let targetNode = null; let findNodesRecursively = (data) => { data.forEach(item => { if (sourceNodeFilterFn(item)) sourceNode = item; if (targetNodeFilterFn(item)) targetNode = item; if (item[childKey]) findNodesRecursively(item[childKey]); }); } findNodesRecursively(treeData); if (!sourceNode || !targetNode) return; let removeSourceNodeRecursively = (data) => { data.forEach((item, index) => { if (item === sourceNode) data.splice(index, 1); else if (item[childKey]) removeSourceNodeRecursively(item[childKey]); }); } removeSourceNodeRecursively(treeData); if (!targetNode[childKey]) targetNode[childKey] = []; targetNode[childKey].push(sourceNode); } ``` > 测试数据 ```javascript let treeData = [ { id: 1, name: 'A', children: [ {id: 2, name: 'B'}, {id: 3, name: 'C'} ] }, { id: 4, name: 'D', children: [ {id: 5, name: 'E'}, {id: 6, name: 'F'} ] } ]; moveTreeNode(treeData, item => item.name === 'B', item => item.name === 'F'); console.log(JSON.stringify(treeData)); ``` #### 查找指定节点方法 ```javascript function findNode(tree, filterFn, childKey = 'children') { if (Array.isArray(tree)) { for (let node of tree) { let result = findNode(node, filterFn, childKey); if (result) { return result; } } } else { if (filterFn(tree)) { return tree; } if (tree[childKey]) { for (let child of tree[childKey]) { let result = findNode(child, filterFn, childKey); if (result) { return result; } } } } return null; } ``` > 测试数据 ```javascript // 定义树结构 const tree = { id: 1, children: [ { id: 2, children: [ { id: 3, name: 'Node 3' }, { id: 4, name: 'Node 4' } ] }, { id: 5, children: [ { id: 6, name: 'Node 6' } ] } ] }; // 测试条件:查找 id 为 3 的节点 const resultNode = findNode(tree, node => node.id === 3, 'children'); if (resultNode) { console.log('找到节点:', resultNode); } else { console.log('未找到节点。'); } ``` #### 删除树指定节点 ```javascript function deleteNodes(tree, filterFn) { if (Array.isArray(tree)) { for (let i = tree.length - 1; i >= 0; i--) { if (filterFn(tree[i])) { tree.splice(i, 1); } else if (typeof tree[i] === 'object') { deleteNodes(tree[i], filterFn); } } } else if (typeof tree === 'object') { for (const key in tree) { if (tree.hasOwnProperty(key) && typeof tree[key] === 'object') { deleteNodes(tree[key], filterFn); } } } } ``` > 测试数据 ```javascript // 示例用法: const tree = { name: 'A', children: [ { name: 'B', children: [ { name: 'C', children: [ { name: 'D', children: [ { name: 'E', }, ], }, ], }, ], }, ], }; const filterFn = (node) => node.name === 'E'; deleteNodes(tree, filterFn); console.log(JSON.stringify(tree, null, 2)); ``` #### 修改树节点信息 ```javascript function modifyNodes(tree, filterFn, callback, childKey = 'children') { if (Array.isArray(tree)) { for (let i = 0; i < tree.length; i++) { if (filterFn(tree[i])) { tree[i] = callback(tree[i]); } if (tree[i][childKey] && Array.isArray(tree[i][childKey])) { modifyNodes(tree[i][childKey], filterFn, callback, childKey); } } } else if (typeof tree === 'object') { if (tree[childKey] && Array.isArray(tree[childKey])) { modifyNodes(tree[childKey], filterFn, callback, childKey); } } } ``` > 测试代码 ```javascript // 示例用法: const tree = { name: 'A', children: [ { name: 'B', children: [ { name: 'C', children: [ { name: 'D', children: [ { name: 'E', }, ], }, ], }, ], }, ], }; const filterFn = (node) => node.name === 'C' || node.name === 'E'; const callback = (node) => { // 修改节点数据 return { ...node, name: node.name + '_Modified', }; }; modifyNodes(tree, filterFn, callback); console.log(JSON.stringify(tree, null, 2)); ```