基准测试比较QSA & .forEach与a NodeIterator
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是什么。
发布于 2011-10-30 14:14:48
由于种种原因,它的速度很慢。最明显的事实是,没有人如此简单地使用它,更少的时间用于优化它。另一个问题是它是大规模重入的,每个节点都必须调用JS并运行过滤器函数。
如果您查看基准第三版,您会发现我添加了迭代器使用getElementsByTagName("*")执行的任务的重新实现,然后在上面运行相同的过滤器。结果显示,它的速度要快得多。去JS -> C++ -> JS是慢的。
在JS ( getElementsByTagName大小写)或C++ ( querySelectorAll情况)中完全过滤节点要比重复跨越边界要快得多。
注意,querySelectorAll使用的选择器匹配也是比较聪明的:它进行从右到左的匹配,并且基于预先计算的缓存(大多数浏览器将遍历类"klass“中所有元素的缓存列表,检查它是否是a元素,然后检查父元素是否是div),因此它们甚至不需要迭代整个文档。
既然如此,什么时候使用NodeIterator?从根本上说,至少在JavaScript里是没有的。在诸如Java这样的语言中(无疑是存在一个名为NodeIterator的接口的主要原因),它可能和任何其他语言一样快,就像过滤器一样使用相同的语言。除此之外,它唯一有意义的地方是在语言中,创建Node对象的内存使用远远大于Node的内部表示。
发布于 2019-10-03 14:45:46
由于各种原因,NodeIterator (和TreeWalker )几乎从未被使用过。这就意味着关于这个主题的信息是稀缺的,而类似于“gsnedders”的答案则是完全没有意义的。我知道这个问题差不多有十年了,所以请原谅我的亡灵。
querySelectorAll这样的方法慢,但这不是您应该测量的性能。关于NodeIterator的问题是,它们是实时的,就像HTMLCollection或LiveNodeList一样,您可以在启动对象一次之后继续使用它。
由NodeList返回的querySelectorAll是静态的,每次需要匹配新添加的元素时都必须重新启动。
这个版本 of jsPerf将NodeIterator放在准备代码中。实际测试只尝试使用iter.nextNode()循环所有新添加的元素。您可以看到,迭代器现在的数量级更快。
done[0-9])。迭代器的速度损失约为10%,而querySelectors损失20%。另一方面,这个版本显示了在选择器的开头添加另一个div >时会发生什么。迭代器的速度损失了33%,而querySelectors得到了10%的速度增加。
在选择器开始时删除初始div > (如在这个版本中)显示,这两种方法都变得更慢,因为它们比早期版本匹配得更多。与预期一样,在这种情况下,迭代器相对于querySelectors具有更高的性能。
这意味着根据节点自身的属性(类、属性等)进行过滤。在NodeIterator中可能更快,而有许多组合子(>,+,~,等等)在您的选择器中,可能意味着querySelectorAll更快。
对于空间(空间)组合器来说尤其如此。使用querySelectorAll('article a')选择元素要比手动遍历每个a元素的所有父元素要容易得多,寻找一个具有tagName of 'ARTICLE'的元素。
在第3.2节中,我给出了一个例子,说明如果您想要与空间组合器相反的功能(不包括带有a祖先的article标记),那么完全相反的方法是正确的。
3个不可能的选择器
3.1简单的层次关系
当然,手动过滤元素实际上给了您无限的控制。这意味着您可以筛选出通常无法与CSS选择器匹配的元素。例如,CSS选择器只能“回首”,因为在div之前选择另一个div是可能的。选择后面跟着另一个div的div是不可能的。
但是,在NodeFilter内部,您可以通过检查node.nextElementSibling.tagName === 'DIV'来实现这一点。CSS选择器无法做出的每一个选择都是如此。
3.2更全面的层次关系
关于NodeFilter的使用,我个人喜欢的另一件事是,当传递给TreeWalker时,可以通过返回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必须提供两个主要优势:
NodeFilter.FILTER_REJECT示例有多有用)但是,当下列任何一项为真时,请不要使用NodeIterator:
body.no-js article > div > div a[href^="/"])
很抱歉,答案很长:)
https://stackoverflow.com/questions/7941288
复制相似问题