npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

js-tree-toolkit

v1.0.3

Published

js树处理工具类

Downloads

2

Readme

数组转树

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;
}

测试代码

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');

树转数组

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);

获取指定层级父节点的方法 默认获取父亲

 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);
    }

测试代码

// 示例用法:
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: [...] }

计算叶子节点的个数

  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;
      }
  }

测试代码

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的层级补充到指定层级

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));
    }
}

测试代码

let tree = {
  value: 1,
  children: [
    {
      value: 2,
      children: [
        {
          value: 3
        }
      ]
    },
    {
      value: 4
    }
  ]
};

fillTree(tree, 3, () => ({value: 0}));
console.log(JSON.stringify(tree, null, 2));

获取数组中对应的子节点

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;
}

测试代码

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);

获取数组中父节点的方法

  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;
  }

测试代码

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);

树节点移动方法

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);
}

测试数据

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));

查找指定节点方法

    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;
    }

测试数据

// 定义树结构
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('未找到节点。');
  }

删除树指定节点

   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);
                }
            }
        }
    }

测试数据

// 示例用法:
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));

修改树节点信息

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);
    }
  }
}

测试代码

// 示例用法:
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));