遍历

“DOM2级遍历和范围”模块定义了两个用于辅助完成顺序遍历DOM结构的类型: NodeIterator和TreeWalker。这两个类型能够基于给定的起点对DOM结构执行深度优先( depth-first )的遍历操作。

DOM遍历是深度优先的DOM结构遍历,也就是说,移动的方向至少有两个(取决于使用的遍历类型)。遍历以给定节点为根,不可能向上超出DOM树的根节点。
在这里插入图片描述

任何节点都可以作为遍历的根节点。如果假设元素为根节点,那么遍历的第一步 就是访问<p>元素,然后再访问同为<body>元素后代的两个文本节点。不过,这次遍历永远不会到达<html><head>元素,也不会到达不属于<body>元素子树的任何节点。而以document为根节点的遍历则可以访问文档中的全部节点。

下图展示了对以document为根节点的DOM树进行深度优先遍历的先后顺序。NodeIterator 和TreeWalker都以这种方式遍历。
在这里插入图片描述

1、NodeIterator

使用document.createNodeIterator() 方法来创建它的实例。这个方法接收下列4个参数:

  • root:想要作为搜索起点的数中的节点。
  • whatToShow:表示想要访问哪些节点的数字代码。
  • filter:是一个NodeFilter对象,或者一个表示应该接受还是某种特定节点的函数。(类似节点过滤器的函数)
  • entityReferenceExpansion:布尔值表示是否扩展实体引用。这个参数在HTML页面中没有用,因为其中的实体引用不能扩展。

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

  • NodeFilter . SHOW_ ALL:显示所有类型的节点。
  • NodeFilter . SHOW_ ELEMENT:显示元素节点。
  • NodeFilter . SHOW_ ATTRIBUTE:显示特性节点。由于DOM结构原因,实际上不能使用这个值。
  • NodeFilter . SHOW_ TEXT:显示文本节点。
  • NodeFilter . SHOW_ CDATA_ SECTION: 显示CDATA节点。对HTML页面没有用。
  • NodeFilter . SHOw_ ENTITY_ REFERENCE:显示实体引用节点。对HTML页面没有用。
  • NodeFil ter . SHOW_ ENTITYE:显示实体节点。对HTML页面没有用。
  • NodeFil ter . SHOW_ PROCESSING_ INSTRUCTION:显示处理指令节点。对HTML页面没有用。
  • NodeFilter . SHOW_ COMMENT:显示注释节点。
  • NodeFilter . SHOW_ DOCUMENT: 显示文档节点。
  • NodeFilter . SHOW_ DOCUMENT_TYPE: 显示文档类型节点。
  • NodeFil ter . SHOW_ DOCUMENT_ FRAGMENT:显示文档片段节点。对HTML页面没有用。
  • NodeFilter . SHOW_ NOTATION:显示符号节点。对HTML页面没有用。

除了NodeFilter.SHOW_ ALL之外,可以使用按位或操作符来组合多个选项,

1
var whatToShow = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT;

可以通过createNodeIterator()方法的filter参数来指定自定义的NodeFilter对象,或者指定一个功能类似节点过滤器( node filter )的函数。
1
2
3
4
5
var filter = function(node) {
return node.tagName.toLowerCase() == "li"?
NodeFilter.FILTER_ACCEPT:
NodeFilter.FILTER_SKIP;
}

NodeIterator类型的两个主要方法是nextNode ()和previousNode()。顾名思义,在深度优先的DOM子树遍历中,nextNode ()方法用于向前前进一步,而previousNode ()用于向后后退一步。在刚刚创建的NodeIterator对象中,有一个内部指针指向根节点,因此第一次调用nextNode()会返回根节点。当遍历到DOM子树的最后一个节点时,nextNode() 返回null。previousNode()方法的工作机制类似。当遍历到DOM子树的最后一个节点,且previousNode()返回根节点之后,再次调用它就会返回null。


小例子:遍历指定节点中 <li>的元素,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var mylist = document.getElementById("mylist");  //获取我们遍历起点的节点。

var filter = function(node) {
return node.tagName.toLowerCase() == "li"?
NodeFilter.FILTER_ACCEPT:
NodeFilter.FILTER_SKIP;
}

var iterator = document.createNodeIterator(mylist, NodeFilter.SHOW_ELEMENT, filter, false);
var node = iterator.nextNode;
while (node !== null) {
console.log(node.tagName) //输出标签名
node = iterator.nextNode();
}

TrreWalker

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

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

创建Treewalker对象要使用document. createTreewalker()方法,这个方法接受的4个参数与document . createNodeIterator ()方法相同:作为遍历起点的根节点、要显示的节点类型、过滤器和一个表示是否扩展实体引用的布尔值。由于这两个创建方法很相似,所以很容易用Treewalker 来代替NodeIterator,如下面的例子所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
var mylist = document.getElementById("mylist");
var filter = function(node) {
return node.tagName.toLowerCase() == "li" ?
NodeFilter.FILTER_ACCEPT :
NodeFilter.FILTER_SKIP;
// NodeFilter.FILTER_REJECT;
}
var walker = document.createTreeWalker(mylist, NodeFilter.SHOW_ELEMENT, filter, false);
var node = walker.nextNode;
while (node !== null) {
console.log(node.tagName) //输出标签名
node = walker.nextNode();
}

在这里,filter可以返回的值有所不同。除了NodeFilter .FILTER_ACCEPT 和NodeFilter.FILTER_ SKIP之外,还可以使用NodeFilter . FILTER_REJECT。 在使用NodeIterator对象时,NodeFilter . FILTER_ SKIP与NodeFilter . FILTER_ REJECT 的作用相同:跳过指定的节点。但在使用TreeWalker对象时,NodeFilter . FILTER SKIP会跳过相应节点继续前进到子树中的下一个节点,而NodeFilter . FILTER REJECT 则会跳过相应节点及该节点的整个子树。例如,将前面例子中的NodeFilter . FILTER_ SKIP 修改成NodeFilter. FILTER_ REJECT, 结果就是不会访问任何节点。这是因为第一个返回的节点是<div>,它的标签名不是”li”,于是就会返回NodeFilter . FILTER REJECT ,这意味着遍历会跳过整个子树。在这个例子中,<div>元素是遍历的根节点,于是结果就会停止遍历。

当然,Treewalker真正强大的地方在于能够在DOM结构中沿任何方向移动。使用Treewalker遍历DOM树,即使不定义过滤器,也可以取得所有<li>元素,如下面的代码所示。(前提我们知道它的结构树)

1
2
3
4
5
6
7
8
9
10
11
var mylist = document.getElementById("mylist");
var walker = document.createTreeWalker(mylist, NodeFilter.SHOW_ELEMENT, null, false);

walker.firstChild(); //转到 <p>
walker.nextSibling() //转到<ul>

var node = walker.firstChild();
while (node !== null) {
console.log(node.tagName)
node = walker.nextSibling();
}

它的HTML结构

1
2
3
4
5
6
7
8
<div id="mylist">
<p>sdfsdf</p>
<ul>
<li id="li"></li>
<li></li>
<li></li>
</ul>
</div>

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

1
2
3
var node = walker.nextNode;
console.log(node === walker.currentNode); //ture
walker.currentNode = document.body; //修改起点

下面的例子会返回body中所有的 li 节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var mylist = document.getElementById("mylist");
var filter = function(node) {
return node.tagName.toLowerCase() == "li" ?
NodeFilter.FILTER_ACCEPT :
NodeFilter.FILTER_SKIP;
// NodeFilter.FILTER_REJECT;
}
var walker = document.createTreeWalker(mylist, NodeFilter.SHOW_ELEMENT, filter, false);
var node = walker.nextNode;
console.log(node === walker.currentNode); //ture
walker.currentNode = document.body; //修改起点
while (node !== null) {
console.log(node.tagName)
node = walker.nextNode();
}


愿你的坚持终有收获。