何时使用NodeIterator

时间:2011-10-29 19:52:07

标签: javascript dom dom4

Benchmark比较QSA& .forEachNodeIterator

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    
}

现在NodeIterator很糟糕,或者我做错了。

问题:我应该何时使用NodeIterator

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

2 个答案:

答案 0 :(得分:10)

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

如果查看revision three of the benchmark,您会发现我已经使用getElementsByTagName("*")添加了迭代器正在执行的操作的重新实现,然后在其上运行相同的过滤器。结果显示,它的速度更快。去JS - > C ++ - > JS很慢。

完全用JS(getElementsByTagName情况)或C ++(querySelectorAll情况)过滤节点比通过反复越过边界快得多。

另请注意,querySelectorAll使用的选择器匹配相对比较智能:它从右到左匹配并基于预先计算的缓存(大多数浏览器将迭代所有元素的缓存列表类“klass”,检查它是否为a元素,然后检查父元素是否为div),因此他们甚至不会为遍历整个文档而烦恼。

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

答案 1 :(得分:5)

由于各种原因,几乎从未使用

NodeIterator(和TreeWalker)。这意味着有关该主题的信息稀缺,answers like @gsnedders'成为了主题,这完全没有标记。我知道这个问题已经有将近十年的历史了,请原谅我的巫术。

1。启动与效果

的确,NodeIterator初始化querySelectorAll之类的方法要慢得多,但这不是您应该测量的性能。

关于NodeIterator的事情是,它们像“ HTMLCollection或实时NodeList一样活着,您可以在初始化一次之后继续使用它” 。
NodeList返回的querySelectorAll是静态的,每次您需要匹配新添加的元素时都必须重新启动。

jsPerf的

This versionNodeIterator放在准备代码中。实际测试仅尝试使用iter.nextNode()遍历所有新添加的元素。您可以看到迭代器现在快了几个数量级。

2。选择器性能

好的,很酷。缓存迭代器更快。 This version显示了另一个显着差异。我添加了10个选择器不应匹配的类(done[0-9])。迭代器的速度损失 10%,而querySelectors的速度损失 20%

另一方面,this version显示了在选择器的开头添加另一个div >时发生的情况。迭代器的速度损失 33%,而querySelectors的速度增加 10%

与选择this version一样,在选择器的开头

删除初始的div >表明这两种方法变得较慢,因为它们比早期版本匹配得多。如预期的那样,在这种情况下,迭代器的性能要比querySelectors相对更高。

这意味着基于节点自身属性(其类,属性等)的过滤在NodeIterator中可能更快,同时具有许多组合符(>,+,〜等)在您的选择器中可能意味着querySelectorAll更快。
对于(space)组合器尤其如此。与手动循环遍历每个querySelectorAll('article a')元素的所有父元素,寻找atagName的元素相比,使用'ARTICLE'选择元素要容易得多。

P.S。在§3.2中,我举了一个示例,说明了如果您希望使用空间组合器的相反功能( exclude a带有祖先article )。

3个不可能的选择器

3.1简单的层次关系

当然,手动过滤元素实际上可以实现无限的控制。这意味着您可以过滤掉通常无法与CSS选择器匹配的元素。例如,CSS选择器只能以“回头”的方式选择div来选择在另一个div之后的div + div。无法选择另外一个div之后的div

但是,在NodeFilter内,您可以通过选中node.nextElementSibling.tagName === 'DIV'来实现。 CSS选择器无法进行的每个选择也是如此。

3.2更多的全局层次关系

我个人对NodeIterator感兴趣的另一件事是,您可以通过返回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
})

NodeFilter中,您只需键入此

return node.tagName === 'ARTICLE' ? NodeFilter.FILTER_REJECT : // ✨ Magic happens here ✨
       node.tagName === 'A'       ? NodeFilter.FILTER_ACCEPT :
                                    NodeFilter.FILTER_SKIP

总结

您不必每次都需要遍历相同类型的节点时才启动API。可悲的是,这个假设是根据提出的问题做出的,而+500的答案(给予了更多的功劳)甚至无法解决错误或NodeIterator所带来的任何好处。

NodeIterator有两个主要优点:

  • 第1章
  • 中讨论的生活热情
  • 高级过滤,如§3
    所述 (我无法强调NodeFilter.FILTER_REJECT示例的用处)

但是,如果满足以下任一条件,请不要使用NodeIterator

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


抱歉,答案很长:)