Iterating with NodeIterator

A NodeIterator flattens the nodes under a root, root first, into depth-first document order; nextNode() and previousNode() move through that list and return null at the ends.

A filtered NodeIterator that survives node removalHTMLLive
<main hidden><p>A</p><span>Z <i>z</i></span><p>B<b>1</b></p><!-- hi --><p>C</p></main>
<pre id="log"></pre>
<script type="module">
  const out = [];
  const iterator = document.createNodeIterator(document.querySelector('main'),
    NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT,
    (node) => (node.nodeName === 'SPAN' ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT));
  for (let node; (node = iterator.nextNode()); ) {
    out.push(`${node.nodeName.padEnd(9)} ${JSON.stringify(node.textContent)}`);
    if (node.nodeName === 'B') node.parentNode.remove();      // removing nodes mid-walk is safe
  }
  out.push(`previousNode() at the end: ${JSON.stringify(iterator.previousNode().textContent)}`);
  document.querySelector('#log').textContent = out.join('\n');
</script>
Browser output of Listing 8.36
Browser output of 36

For an iterator, FILTER_REJECT means the same as FILTER_SKIP: the <span> is dropped but its <i> is still visited. Removing nodes does not break the loop, because the iterator re-anchors its referenceNode. Stepping back at the end returns the last node again, since the pointer sits after it.