/** * 用于遍历的节点接口 */ export interface INode { children?: Array, [prop: string]: any, } /** * 用于遍历的节点信息 */ export interface INodeInfo { node: T, parent?: T, stack?: Array, nodePath?: string, jsonPath?: string, index?: number, key?: string | number, } export interface TraverseOptions { mode?: 'onlyChildren' | 'onlyArray' | 'anyObject', depthFirst?: boolean, excludedKeySet?: Set, } function handleNext( func: (next: INodeInfo) => any, current: INodeInfo, options?: TraverseOptions, ) { if (options.mode === 'onlyChildren') { const list = current.node && current.node.children; if (!list) return; for (let index = 0; index < list.length; index++) { const child = list[index]; if (!child) continue; const nodeInfo: INodeInfo = { node: child as T, parent: current.node, stack: current.stack.concat([current.node]), nodePath: `${current.nodePath}/${index}`, jsonPath: `${current.jsonPath}.children[${index}]`.replace(/^\./, ''), index, key: index, }; const result = func(nodeInfo); // if (result !== undefined) // return result; } } else if (options.mode === 'onlyArray') { for (const key in current.node) { if (!current.node.hasOwnProperty(key) || options.excludedKeySet.has(key)) continue; const value = current.node[key]; if (!Array.isArray(value)) continue; const list = value; for (let index = 0; index < list.length; index++) { const child = list[index]; if (!child) continue; const nodeInfo: INodeInfo = { node: child as T, parent: current.node, stack: current.stack.concat([current.node]), nodePath: `${current.nodePath}/${index}`, jsonPath: `${current.jsonPath}.${key}[${index}]`.replace(/^\./, ''), index, key: index, }; const result = func(nodeInfo); // if (result !== undefined) // return result; } } } else if (options.mode === 'anyObject') { for (const key in current.node) { if (!current.node.hasOwnProperty(key) || options.excludedKeySet.has(key)) continue; const value = current.node[key]; if (Array.isArray(value)) { const list = value; for (let index = 0; index < list.length; index++) { const child = list[index]; if (!child) continue; const nodeInfo: INodeInfo = { node: child as T, parent: current.node, stack: current.stack.concat([current.node]), nodePath: `${current.nodePath}/${index}`, jsonPath: `${current.jsonPath}.${key}[${index}]`.replace(/^\./, ''), index, key: index, }; const result = func(nodeInfo); // if (result !== undefined) // return result; } } else if (typeof value === 'object' && value !== null) { const nodeInfo: INodeInfo = { node: value as T, parent: current.node, stack: current.stack.concat([current.node]), nodePath: `${current.nodePath}/${key}`, jsonPath: `${current.jsonPath}['${key}']`, index: undefined, key, }; const result = func(nodeInfo); // if (result !== undefined) // return result; } } } } /** * 遍历树 * @param func * @param init 初始点信息 * @param options.mode 'onlyChildren' 表示只向下遍历 children 的元素(默认);'onlyArray' 表示只向下遍历 Array 的元素;'anyObject' 表示只要属性为 Object 类型都会遍历 * @param options.depthFirst 默认广度优先搜索,是否深度优先搜索 * @example traverse((current) => console.log(current.node.name), { init: rootNode }); * @example traverse((current) => console.log(current.jsonPath), { init: ast }, { mode: 'anyObject' }); */ export function traverse( func: (current: INodeInfo) => any, init: INodeInfo, options?: TraverseOptions, ) { init = Object.assign({ parent: undefined, stack: [], nodePath: '', jsonPath: '', index: undefined }, init); options = Object.assign({ mode: 'onlyChildren', depthFirst: false, excludedKeySet: new Set() }, options); if (options.depthFirst) { func(init); handleNext((next) => traverse(func, next, options), init, options); } else { let queue: Array> = []; queue = queue.concat(init); let current: INodeInfo; while ((current = queue.shift())) { const result = func(current); if (result !== undefined) return result; handleNext((next) => queue.push(next), current, options); } } } export default traverse;