JS之DOM篇-节点遍历

定义

DOM遍历模块定义了用于辅助完成顺序遍历DOM结构的类型:Nodeiterator和TreeWalker,它们能够基于给定的起点对DOM结构执行深度优先(depth-first)的遍历操作

示例HTML

<!DOCTYPE html>
<html>
  <head>
    <title>Example</title>
  </head>
  <body>
    <p><b>Hello</b> world!</p>
  </body>
</html>

示例HTML的DOM结构

以document为根节点的DOM树进行深度优先遍历的先后顺序

任何节点都可以作为遍历的根节点,示例展示了以document为根节点的DOM树进行深度优先遍历的先后顺序,访问的第一个节点是document节点,最后一个节点是包含'world!'的文本节点

从文档最后的文本节点开始,遍历可以反向移动到DOM树的顶端,此时访问的第一个节点是包含"Hello"的文本节点,访问的最后一个节点是document节点。Nodeiterator和TreeWalker都以这种方式执行遍历

NodeIterator()

document.NodeIterator()方法可以创建NodeIterator类型的新实例,该方法接收4个参数

root: 搜索起点
whatToShow: 数字代码,表示要访问哪些节点
filter: NodeFilter对象,或者一个表示应该接受还是拒绝某种节点的函数
entityReferenceExpansion: 布尔值,表示是否要扩展实体引用。由于在HTML页面中,实体引用不能扩展,所以这个参数在HTML页面中没有用

whatToShow参数是一个位掩码,这个参数的值以常量形式在NodeFilter类型中定义,可以通过应用一或多个过滤器(filter)来确定要访问哪些节点

NodeFilter.SHOW_ALL: 显示所有类型的节点
NodeFilter.SHOW_ELEMENT: 显示元素节点
NodeFilter.SHOW_ATTRIBUTE: 显示特性节点。由于DOM结构原因,实际上不能使用这个值
NodeFilter.SHOW_TEXT: 显示文本节点
NodeFilter.SHOW_CDATA_SECTION: 显示CDATA节点。对HTML页面没有用
NodeFilter.SHOW_ENTITY_REFERENCE: 显示实体引用节点。对HTML页面没有用
NodeFilter.SHOW_ENTITYE: 显示实体节点。对HTML页面没有用
NodeFilter.SH0W_PROCESSING_INSTRUCTION: 显示处理指令节点。对HTML页面没有用
NodeFi1ter.SHOW_COMMENT: 显示注释节点
NodeFilter.SHOW_DOCUMENT: 显示文档节点
NodeFilter.SHOW_DOCUMENT_TYPE: 显示文档类型节点
NodeFilter.SHOW_DOCUMENT_FRAGMENT: 显示文档片段节点。对HTML页面没有用
NodeFilter.SHOW_NOTATION: 显示符号节点。对HTML页面没有用

创建一个只显示<p>元素的节点迭代器

var filter = {
  acceptNode: function(node) {
    return node.tagName.toLowerCase() === 'p' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
  }
}

var iterator = document.createNodeIterator(document, NodeFilter.SHOW_ELEMENT, filter, false)

通过createNodeIterator()方法的filter参数,可以自定义NodeFilter对象,每个NodeFilter对象有且只有一个acceptNode()方法,如果应该访问给定的节点,该方法返回NodeFilter.FILTER_ACCEPT,如果不应该访问给定的节点,该方法返回NodeFilter.FILTER_SKIP。由于NodeFilter是一个抽象类型,因此不能直接创建它的实例,可以像示例中一样,创建一个包含acceptNode()方法的对象,然后传给createNodeIterator()方法

filter也可以是一个与acceptNode()方法类似的函数

var filter = function(node) {
  return node.tagName.toLowerCase() === 'p' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
}

var iterator = document.createNodeIterator(document, NodeFilter.SHOW_ELEMENT, filter, false)

如果不指定过滤器,那么应该在第三个参数的位置上传入null

var iterator = document.createNodeIterator(document, NodeFilter.SHOW_ALL, null, false)

NodeIterator类型有两个主要的方法:nextNode()和previousNode()。在深度优先的DOM树遍历中,nextNode()方法用于向前前进一步,previousNode()用于向后后退一步

<div id="box">
  <p>列表</p>
  <ul>
    <li>one</li>
    <li>two</li>
  </ul>
</div>
<script>
  var iterator = document.createNodeIterator(box, NodeFilter.SHOW_ELEMENT, null, false)
  var node = iterator.nextNode()
  while(node !== null) {
    console.log(node.tagName)
    node = iterator.nextNode()
  }
  // DIV
  // P
  // UL
  // LI
  // LI
</script>

这个示例遍历了box元素的所有子元素,第一调用nextNode()方法会返回<p>元素,由于在到达DOM树末端时nextNode()返回null,所以可以用while语句检测每次循环时node值是否等于null

如果只想获取<li>元素,可以加一个过滤器

<div id="box">
  <p>列表</p>
  <ul>
    <li>one</li>
    <li>two</li>
  </ul>
</div>
<script>
  var filter = function(node) {
    return node.tagName.toLowerCase() === 'li' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
  }
  var iterator = document.createNodeIterator(box, NodeFilter.SHOW_ELEMENT, filter, false)
  var node = iterator.nextNode()
  while(node !== null) {
    console.log(node.tagName)
    node = iterator.nextNode()
  }
  // LI
  // LI
</script>

由于nextNode()和previousNode()方法都基于NodeIterator在DOM结构中的内部指针工作,所以DOM结构的变化会反映在遍历的结果中

TreeWalker

TreeWalker是NodeIterator的一个高级版本,除了包括nextNode()和previous()在内的相同功能外,还提供了用于在不同方向上遍历DOM结构的方法

parentNode():遍历到当前节点的父节点
firstChild():遍历到当前节点的第一个子节点
lastChild():遍历到当前节点的最后一个子节点
nextSibling():遍历到当前节点的下一个同辈节点
previousSibling():遍历到当前节点的上一个同辈节点

document.createTreeWalker()方法可以创建TreeWalker对象,它接受和document.createNodelterator()方法相同的4个参数。下面使用TreeWalker代替NodeIterator遍历box元素节点

<div id="box">
  <p>列表</p>
  <ul>
    <li>one</li>
    <li>two</li>
  </ul>
</div>
<script>
  var filter = function(node) {
    return node.tagName.toLowerCase() === 'li' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
  }
  var iterator = document.createTreeWalker(box, NodeFilter.SHOW_ELEMENT, filter, false)
  var node = iterator.nextNode()
  while(node !== null) {
    console.log(node.tagName)
    node = iterator.nextNode()
  }
  // LI
  // LI
</script>

TreeWalker的filter参数返回值与NodeIterator的有所不同,它可以返回NodeFilter.FILTER_ACCEPT、NodeFilter.FILTER_SKIP和NodeFilter.FILTER_REJECT三种值。在使用TreeWalker对象时,NodeFilter.FILTER_SKIP表示跳过相应节点继续前进到子树中的下一个节点,NodeFilter.FILTER_REJECT表示跳过指定的节点。也就是说,在TreeWalker对象中使用NodeFilter.FILTER_REJECT与在NodeIterator对象中使用NodeFilter.FILTER_SKIP效果是相同的;在NodeIterator对象中,也可以返回NodeFilter.FILTER_REJECT,只不过NodeFilter.FILTER_SKIP和NodeFilter.FILTER_REJECT结果是一样的

由于TreeWalker能够在DOM结构中沿任何方向移动,所以即使不定义过滤器,也可以取得所有<li>元素

<div id="box">
  <p>列表</p>
  <ul>
    <li>one</li>
    <li>two</li>
  </ul>
</div>
<script>
  var walker = document.createTreeWalker(box, NodeFilter.SHOW_ELEMENT, null, false)

  walker.firstChild() // 转到p节点
  walker.nextSibling() // 转到ul节点
  var node = walker.firstChild() // 转到第一个li节点

  while(node !== null) {
    console.log(node.tagName)
    node = walker.nextNode()
  }
  // LI
  // LI
</script>

TreeWalker类型还有一个currentNode属性,表示任何遍历方法在上一次遍历中返回的节点。通过设置这个属性还可以修改遍历继续进行的起点

var node = walker.nextNode()
console.log(node === walker.currentNode) // true
walker.currentNode = document.body // 修改起点
posted @ 2021-09-29 13:44  wmui  阅读(1599)  评论(0编辑  收藏  举报