自定义步进树遍历生成器

时间:2020-07-28 07:47:16

标签: javascript typescript iterator generator

我想使用带有自定义匹配器回调的生成器遍历dom树,当产生值时,返回一个在其间有遍历节点的数组。说我有这个结构。

  root
  / \
P1   P2
 |   |
T1   T2

我想做iter.next(isP)iter.next(isText)更新匹配器,直到下一个节点匹配。

type Matcher = (node: INode) => boolean
export function* nextNode(node: INode, matcher: Matcher = () => true, store: []): Generator<INode, any, Matcher> {
  let reset: Matcher = matcher
  store = store.concat(node)
  if (reset(node)) {
    reset = yield store
    store = []
  }
  if (node.children) {
    for (let childNode of node.children) {
      yield *nextNode(childNode, matcher, store)
    }
  }
  return
}

我的代码问题是弹出函数调用堆栈时reset丢失了。例如,如果我在T1中,而以前的堆栈是isText,那么现在如果我这样做了,iter.next(isP)将行不通。我该怎么办?

const iter = nextNode(root, isT)
iter.next() <-- this is T1
iter.next(isP) <-- this is T2 should be P2

2 个答案:

答案 0 :(得分:1)

您可以使用生成器的返回值来穿越遍历状态。当yield*从root的第一个孩子返回时,它将需要给您提供在产生root和p1之后在next调用中可用的存储和匹配器。

…
if (node.children) {
  for (let childNode of node.children) {
    [reset, store] = yield* nextNode(childNode, reset, store)
//  ^^^^^^^^^^^^^^^^^                           ^^^^^
  }
}
return [reset, store]
//     ^^^^^^^^^^^^^^

完整的示例:

function* nextNode(node, matcher = () => true, store = []) {
  store.push(node.name)
  if (matcher(node)) {
    matcher = yield store
    store = []
  }
  if (node.children) {
    for (let childNode of node.children) {
      [matcher, store] = yield* nextNode(childNode, matcher, store)
    }
  }
  return [matcher, store]
}

const node = (name, children) => ({name, children})
const is = c => n => n.name[0] == c

const iter = nextNode(node("root", [
  node("p1", [node("t1")]),
  node("p2", [node("t2")])
]), is("t"))
console.log("until next t:", iter.next())
console.log("until next p:", iter.next(is("p")))
console.log("until next p:", iter.next(is("p")))

答案 1 :(得分:0)

嗯,似乎一个简单的解决方案是只拥有一个全局匹配器。

type Matcher = (node: INode) => boolean
type TYield = INode[]
function* nextNode(
  node: INode,
  matcher: Matcher = () => true,
): Generator<TYield, TYield, Matcher> {
  let store: INode[] = []
  let reset = matcher
  function* _nextNode(node: INode): Generator<TYield, any, Matcher> {
    store.push(node)
    if (reset(node)) {
      reset = yield store
      if (!reset) reset = matcher
      store = []
    }
    if (node.children) {
      for (const childNode of node.children) {
        yield* _nextNode(childNode)
      }
    }
    return
  }
  yield* _nextNode(node)
  return store
}