何时使用NodeIterator

Ray*_*nos 9 javascript dom dom4

Benchmark比较QSA和.forEachaNodeIterator

toArray(document.querySelectorAll("div > a.klass")).forEach(function (node) {
  // do something with node
});

var filter = {
    acceptNode: function (node) {
        var condition = node.parentNode.tagName === "DIV" &&
            node.classList.contains("klass") &&
            node.tagName === "A";

        return condition ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
    }  
}
// FIREFOX Y U SUCK
var iter = document.createNodeIterator(document, NodeFilter.SHOW_ELEMENT, filter, false);
var node;
while (node = iter.nextNode()) {
    // do thing with node    
}
Run Code Online (Sandbox Code Playgroud)

现在要么NodeIterator糟糕,要么我做错了.

问题:NodeIterator什么时候应该使用?

如果您不知道,DOM4指定NodeIterator是什么.

Gus*_*Wal 24

NodeIterator(并且TreeWalker,就此而言)由于各种原因几乎从未使用过。这意味着有关该主题的信息很少,并且出现了诸如@gsnedders 之类的答案,这完全不符合标准。我知道这个问题已经有将近十年的历史了,所以请原谅我的死灵术。

1. 启动与表现

的确,在开始NodeIterator是waaay慢于类似的方法querySelectorAll,但是这不是你应该测量性能。

关于NodeIterators的事情是它们是实时的,就像 anHTMLCollection或 live 一样NodeList,您可以在启动一次后继续使用该对象。
NodeList由归国querySelectorAll是静态的,必须重新启动每次你需要搭配新添加元素的时间。

此版本的jsPerf 将NodeIterator编写代码。实际测试仅尝试使用iter.nextNode(). 您可以看到迭代器现在快了几个数量级。

2. 选择器性能

好吧,爽。缓存迭代器更快。然而,这个版本显示了另一个显着差异。我添加了 10 个done[0-9]选择器不应该匹配的类 ( )。迭代器损失了大约10%的速度,而 querySelectors 损失了20%

另一方面,这个版本显示了div >在选择器的开头添加另一个时会发生什么。迭代器失去了33%的速度,而 querySelectors 的速度增加10%

div >此版本中删除选择器开头的首字母中表明这两种方法都变慢了,因为它们比早期版本匹配得更多。与预期的一样,在这种情况下,迭代器的性能相对比 querySelectors 更高。

这意味着基于节点自己的属性(它的类、属性等)的过滤在 a 中可能更快NodeIterator,而在您的选择器中有很多组合符(>、+、~ 等)可能意味着querySelectorAll更快。
对于 (空间)组合器来说尤其如此。选择元素 withquerySelectorAll('article a')比手动循环遍历每个a元素的所有父元素要容易得多,寻找具有 atagName的元素'ARTICLE'

PS 在 §3.2 中,我举了一个例子,说明如果您想要与空间组合器所做的相反的事情(排除 a带有article祖先的。

3 不可能的选择器

3.1 简单的层次关系

当然,手动过滤元素为您提供了几乎无限的控制。这意味着您可以过滤掉通常无法与 CSS 选择器匹配的元素。例如,CSS选择器只能“回头看”的方式,选择div这为S之前被另一个div是可能的div + div。选择divs后跟另一个sdiv是不可能的。

但是,在 a 中NodeFilter,您可以通过检查来实现此目的node.nextElementSibling.tagName === 'DIV'。对于 CSS 选择器无法做出的每个选择,也是如此。

3.2 更全局的层级关系

我个人喜欢NodeIterators 的另一件事是,您可以通过返回NodeFilter.FILTER_REJECT而不是NodeFilter.FILTER_SKIP.

想象一下,您想要遍历a页面上的所有标签,除了具有article祖先的标签。
使用 querySelectors,您可以输入类似

let a = document.querySelectorAll('a')
a = Array.prototype.filter.call(a, function (node) {
  while (node = node.parentElement) if (node.tagName === 'ARTICLE') return false
  return true
})
Run Code Online (Sandbox Code Playgroud)

而在 a 中NodeFilter,你只需要输入这个

return node.tagName === 'ARTICLE' ? NodeFilter.FILTER_REJECT : // ? Magic happens here ?
       node.tagName === 'A'       ? NodeFilter.FILTER_ACCEPT :
                                    NodeFilter.FILTER_SKIP
Run Code Online (Sandbox Code Playgroud)

综上所述

每次需要迭代相同类型的节点时,您不必启动 API。可悲的是,这个假设是在提出问题时做出的,+500 的答案(给予它更多的信任)甚至没有解决错误或任何好处NodeIterator

NodeIterator必须提供两个主要优点:

  • 活力,如 §1 所述
  • 高级过滤,如第 3 节所述
    (我再怎么强调这个NodeFilter.FILTER_REJECT例子的用处都不为过)

但是,NodeIterator当以下任一情况为真时,请勿使用s:

  • 它的实例只会被使用一次/几次
  • 使用 CSS 选择器可以查询复杂的层次关系
    (即body.mobile article > div > div a[href^="/"]


对不起,答案很长:)

  • 这是一流的答案。它涵盖了多种案例,以及每种案例的优缺点,解释了“为什么”事情会这样运作,并在最后提供了精彩的总结。更重要的是,这是在 2019 年底写的。自官方“答案”发布以来的 8 年里,浏览器和网络发生了很多事情。如果可能的话,_this_应该成为2020年和可预见的未来的官方答案。 (2认同)

gsn*_*ers 12

由于各种原因它很慢.最明显的事实是,没有人使用它,所以非常简单地花了很多时间来优化它.另一个问题是它是大量重入的,每个节点都必须调用JS并运行过滤器功能.

如果你看一下基准测试的第三版,你会发现我已经添加了迭代器正在使用的内容的重新实现,getElementsByTagName("*")然后在其上运行相同的过滤器.结果显示,它的速度更快.去JS - > C++ - > JS很慢.

完全用JS(getElementsByTagName案例)或C++(querySelectorAll案例)过滤节点比通过反复跨越边界快得多.

另请注意,选择器匹配使用起来querySelectorAll比较聪明:它从右到左匹配并基于预先计算的缓存(大多数浏览器将迭代所有元素的缓存列表,类别为"klass",检查是否它是一个a元素,然后检查父元素是否为a div),因此它们甚至不会烦扰迭代整个文档.

鉴于此,何时使用NodeIterator?基本上从来没有在JavaScript中,至少.在诸如Java之类的语言中(毫无疑问是有一个名为NodeIterator的接口的主要原因),它可能与其他任何东西一样快,因为那时你的过滤器将使用与过滤器相同的语言.除此之外,唯一有意义的是在语言中,创建Node对象的内存使用量远远大于Node的内部表示.