【问题标题】:Custom stepping tree traversal generator自定义步进树遍历生成器
【发布时间】:2020-07-28 07:47:16
【问题描述】:

我想使用带有自定义匹配器回调的生成器遍历 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

【问题讨论】:

    标签: javascript typescript iterator generator


    【解决方案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")))

    【讨论】:

    • 我试过了,但似乎不起作用,你能告诉我完整的代码吗?我很好,没有 store just yield 节点。
    • 感谢您的回答和解释!我确实意识到它的一个问题是返回和产量类型不一致,所以如果我给出一个错误的匹配器,它将返回 [matcher, nodes] 而不是节点,并且鉴于我们必须将匹配器返回到外部范围,我不'真的不想将匹配器返回给调用者。
    • @Mengo 我假设您不关心生成器的返回类型,并且如果最后一个节点不匹配,则不应生成树的最后一段。不过,您的闭包解决方案可能会更容易。
    【解决方案2】:

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

    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
    }
    

    【讨论】:

    • 我建议使用if (store.length) yield store; 而不是return store,将类型更改为Generator&lt;Inode[], void, Matcher&gt;。使用标准迭代是否更容易?
    • 好建议!我正在尝试验证它的行为方式,但是从 TS 得到这个错误,Cannot iterate value because the 'next' method of its iterator expects type 'Matcher', but for-of will always send 'undefined',我认为在普通 JS 中应该没问题,因为 matcher 有一个默认值,你知道如何用 for..of 传递参数吗?
    • 啊,我觉得应该是Generator&lt;Inode[], void, Matcher?&gt;(或者Matcher | undefined
    猜你喜欢
    • 1970-01-01
    • 2010-09-28
    • 2018-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-26
    • 1970-01-01
    相关资源
    最近更新 更多