Когда использовать 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, но это не та производительность, которую вы должны измерять.

Дело в NodeIterators в том, что они живые в том смысле, что, как 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 могут "оглядываться назад" только так, как выбираютdivs, которым предшествует другойdiv возможно с div + div. Выборdivs, за которыми следует другойdiv невозможно.

Однако внутри 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
})

В то время как в NodeFilter, вам нужно только ввести это

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

В заключение

Вы не запускаете API каждый раз, когда вам нужно перебирать узлы одного и того же типа. К сожалению, это предположение было сделано с заданным вопросом, а ответ +500 (что дает ему гораздо больше) даже не касается ошибки или каких-либо льгот.NodeIterators есть.

Есть два основных преимущества NodeIterators должны предложить:

  • Живучесть, как обсуждалось в §1
  • Расширенная фильтрация, как обсуждалось в §3
    (я не могу не подчеркнуть, насколько полезныNodeFilter.FILTER_REJECT пример)

Однако не используйте NodeIterators, когда выполняется одно из следующих условий:

  • Его экземпляр будет использоваться только один / несколько раз
  • Запрашиваются сложные иерархические отношения, которые возможны с помощью селекторов CSS
    (т. Е.body.mobile article > div > div a[href^="/"])


Извините за долгий ответ:)

Другие вопросы по тегам