Когда использовать NodeIterator
Бенчмарк сравнивает QSA & .forEach
против 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.
1 ответ
Это медленно по разным причинам. Наиболее очевидным является тот факт, что никто не использует его так просто, гораздо меньше времени было потрачено на его оптимизацию. Другая проблема заключается в массовом повторном входе, каждый узел должен вызывать JS и запускать функцию фильтра.
Если вы посмотрите на третью версию эталонного теста, вы обнаружите, что я добавил повторную реализацию того, что делает итератор, используя getElementsByTagName("*")
а затем запустить идентичный фильтр на этом. Как показывают результаты, это значительно быстрее. Идти JS -> C++ -> JS медленно.
Полная фильтрация узлов в JS (getElementsByTagName
случай) или C++ (querySelectorAll
случай) гораздо быстрее, чем делать это, многократно пересекая границу.
Обратите внимание также на соответствие селектора, как используется querySelectorAll
, сравнительно умный: он выполняет сопоставление справа налево и основан на предварительно вычисленных кешах (большинство браузеров перебирают в кэшированном списке всех элементов с классом "klass", проверьте, является ли это a
элемент, а затем проверьте, является ли родитель div
) и, следовательно, они даже не будут перебирать весь документ.
Учитывая, что, когда использовать NodeIterator? По крайней мере, в JavaScript. В таких языках, как Java (несомненно, это основная причина, по которой существует интерфейс с именем NodeIterator), он, скорее всего, будет таким же быстрым, как и все остальное, так как тогда ваш фильтр будет на том же языке, что и фильтр. Кроме того, единственный другой раз, когда это имеет смысл, это в языках, где использование памяти для создания объекта Node намного больше, чем внутреннее представление Node.
NodeIterator
(а также TreeWalker
, если уж на то пошло) почти никогда не используются по разным причинам. Это означает, что информации по теме мало, и появляются ответы типа @gsnedders, которые совершенно не соответствуют действительности. Я знаю, что этому вопросу уже почти десять лет, так что извините за мою некромантию.
1. Запуск и производительность
Это верно, что инициация АNodeIterator
медленнее, чем такой метод, как querySelectorAll
, но это не та производительность, которую вы должны измерять.
Дело в NodeIterator
s в том, что они живые в том смысле, что, как HTMLCollection
или жить NodeList
, вы можете продолжать использовать объект после его однократного запуска.
ВNodeList
вернулся querySelectorAll
статичен, и его нужно будет повторно запускать каждый раз, когда вам нужно сопоставить вновь добавленные элементы.
Эта версия jsPerf помещаетNodeIterator
в коде подготовки. Фактический тест пытается только перебрать все вновь добавленные элементы с помощьюiter.nextNode()
. Вы можете видеть, что итератор теперь на порядки быстрее.
2. Производительность селектора
ОК, круто. Кеширование итератора происходит быстрее. Однако в этой версии есть еще одно существенное отличие. Я добавил 10 классов (done[0-9]
), что селекторы не должны совпадать. Итератор теряет около 10% своей скорости, а querySelectors теряет 20%.
С другой стороны, эта версия показывает, что происходит, когда вы добавляете еще одинdiv >
в начале селектора. Итератор теряет 33% своей скорости, в то время как querySelectors получил УВЕЛИЧЕНИЕ скорости на 10%.
Удаление начальногоdiv >
в начале селектора, как в этой версии, показывает, что оба метода работают медленнее, потому что они больше соответствуют более ранним версиям. Как и ожидалось, итератор в этом случае относительно более производительный, чем querySelectors.
Это означает, что фильтрация на основе собственных свойств узла (его классов, атрибутов и т. Д.), Вероятно, выполняется быстрее в NodeIterator
, при наличии большого количества комбинаторов (>, +, ~ и т.д.) в вашем селекторе, вероятно, означает querySelectorAll
быстрее.
Это особенно актуально для (пробел) комбинатор. Выбор элементов с
querySelectorAll('article a')
намного проще, чем вручную перебирать всех родителей каждого a
элемент, ищем тот, у которого есть tagName
из 'ARTICLE'
.
PS в §3.2 я привожу пример того, как может быть верно прямо противоположное, если вы хотите противоположное тому, что делает комбинатор пространства (исключите a
теги с article
предок).
3 Невозможные селекторы
3.1 Простые иерархические отношения
Конечно, фильтрация элементов вручную дает вам практически неограниченный контроль. Это означает, что вы можете отфильтровать элементы, которые обычно невозможно сопоставить с селекторами CSS. Например, селекторы CSS могут "оглядываться назад" только так, как выбираютdiv
s, которым предшествует другойdiv
возможно с div + div
. Выборdiv
s, за которыми следует другойdiv
невозможно.
Однако внутри NodeFilter
, вы можете добиться этого, проверив node.nextElementSibling.tagName === 'DIV'
. То же самое касается любого выбора, который селекторы CSS сделать не могут.
3.2 Более глобальные иерархические отношения
Еще одна вещь, которая мне нравится лично NodeIterator
s, заключается в том, что вы можете отклонить узел и все его поддерево, вернув 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
s есть.
Есть два основных преимущества NodeIterator
s должны предложить:
- Живучесть, как обсуждалось в §1
- Расширенная фильтрация, как обсуждалось в §3
(я не могу не подчеркнуть, насколько полезныNodeFilter.FILTER_REJECT
пример)
Однако не используйте NodeIterator
s, когда выполняется одно из следующих условий:
- Его экземпляр будет использоваться только один / несколько раз
- Запрашиваются сложные иерархические отношения, которые возможны с помощью селекторов CSS
(т. Е.body.mobile article > div > div a[href^="/"]
)
Извините за долгий ответ:)