javascript实现DOM树先序遍历

DOM 可以将任何 HTML 或 XML 文档描绘成一个由多层节点构成的结构。节点分为几种不同的类
型,每种类型分别表示文档中不同的信息及(或)标记。每个节点都拥有各自的特点、数据和方法,另
外也与其他节点存在某种关系。节点之间的关系构成了层次,而所有页面标记则表现为一个以特定节点
为根节点的树形结构。

DOM树中包含的节点类型

一共有12种Node类型:

  • 元素节点   Node.ELEMENT_NODE(1)
  • 属性节点   Node.ATTRIBUTE_NODE(2)
  • 文本节点   Node.TEXT_NODE(3)
  • CDATA节点 Node.CDATA_SECTION_NODE(4)
  • 实体引用名称节点    Node.ENTRY_REFERENCE_NODE(5)
  • 实体名称节点   Node.ENTITY_NODE(6)
  • 处理指令节点   Node.PROCESSING_INSTRUCTION_NODE(7)
  • 注释节点   Node.COMMENT_NODE(8)
  • 文档节点   Node.DOCUMENT_NODE(9)
  • 文档类型节点   Node.DOCUMENT_TYPE_NODE(10)
  • 文档片段节点   Node.DOCUMENT_FRAGMENT_NODE(11)
  • DTD声明节点 Node.NOTATION_NODE(12)

本文只讨论元素节点的遍历。

在遍历的时候的过滤方式为:

1
2
3
4
5
6
7
8
if(node.nodeType === 1){
console.log(node.tagName);
}

//由于IE8不支持Node对象写法,所以在IE8中,以下写法报错
console.log(Node.ELEMENT_NODE); //Node未定义

//故为了兼容,直接使用`1`代替元素节点。

先序遍历DOM节点

以下面DOM结构为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"><title>Title</title>
</head>
<body>
<div id="wrapper" class="wrapper">
<section class="header">
<div class="logo"></div>
</section>
<section class="main">
<div class="sidebar">
<ul class="menu">
<li>
<a href=""></a>
</li>
<li>
<a href=""></a>
</li>
</ul>
</div>
</section>
<section class="footer">
<div class="copyright"></div>
</section>
</div>
</body>
</html>

使用DOM1中的基础接口递归遍历

DOM1中为基础类型Node提供了一些api,通过这些api可以完成一些基础的DOM操作。使用递归遍历DOM树的代码比较简单,核心思想就是先处理当前节点,然后再从左到右递归遍历子节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function traversal(node){
//对node的处理
if(node && node.nodeType === 1){
console.log(node.tagName);
}
let childNodes = node.childNodes,
item;
for(let i = 0 ; i < childNodes.length ; i++){
item = childNodes[i];
if(item.nodeType === 1){
//递归先序遍历子节点
traversal(item);
}
}
}
traversal(document.getElementById('wrapper'));

使用DOM扩展的Element Traversal API,递归遍历DOM树

新增Element Traversal API:

  1. firstElementChild:第一个孩子元素
  2. lastElementChild:最后一个孩子元素
  3. previousElementSibling:上一个相邻的元素
  4. nextElementSibing:下一个相邻的元素
  5. childElementCount:元素子节点的数目
1
2
3
4
5
6
7
8
9
10
11
12
function traversalUsingTraversalAPI(node){
if(node && node.nodeType === 1){
console.log(node.tagName);
}
let len = node.childElementCount,
child = node.firstElementChild;
for(let i = 0 ; i < len ; i++){
traversalUsingTraversalAPI(child);
child = child.nextElementSibling;
}
}
traversalUsingTraversalAPI(document.getElementById('wrapper'))

使用NodeIterator

NodeIterator 类型是两者中比较简单的一个,可以使用 document.createNodeIterator()方
法创建它的新实例。这个方法接受下列 4 个参数。

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

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

1
2
3
4
5
6
7
8
9
function traversalUsingNodeIterator(node){
let iterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT,null,false);
node = iterator.nextNode();
while(node != null){
console.log(node.tagName);
node = iterator.nextNode();
}
}
traversalUsingNodeIterator(document.getElementById('wrapper'))

使用TreeWalker

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
function traversalUsingTreeWalker(node){
let treeWalker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT,null,false);
if(node && node.nodeType === 1){
console.log(node.tagName);
}
node = treeWalker.nextNode();
while(node != null){
console.log(node.tagName);
node = treeWalker.nextNode();
}
}
traversalUsingTreeWalker(document.getElementById('wrapper'))

Reference

先序遍历DOM树的5种方法