DOM2 级标准定义了两个用于完成顺序遍历 DOM 结构的类型:NodeIterator 和 TreeWalker。它们都能够基于给定的起点对 DOM 结构进行深度优先遍历。与 DOM 兼容的浏览器(Firefox 1+、Safari 1.3+、Opera 7.6+、Chrome 0.2+)都支持这两个类型。IE 不支持!以下代码可以检测浏览器是否支持 DOM2 级遍历:
var supportsTraversals = document.implementation.hasFeature("Traversal","2.0");
var supportsNodeIterator = (typeof document.createNodeIterator == "function");
var supportsTreeWalker = (typeof document.createTreeWalker == "function");
遍历以给定的节点为根,不会超出 DOM 树的根节点! 我们以下面的 HTML 页面为例:
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
<p><b>Hello</b> world!</p>
</body>
</html>
下面的图示给出了深度遍历的顺序:
1 NodeIterator
使用 document.createNodeIterator() 方法创建一个 NodeIterator 实例。它接受四个参数:
参数名 | 说明 |
---|---|
root | 树中某个节点作为搜索起点。 |
whatToShow | 要访问哪些节点的数字代码。 |
filter | NodeFilter 对象,用于过滤某些节点的函数。 |
entityReferenceExpansion | 布尔值,是否扩展实体引用。它在 HTML 中无用处。 |
whatToShow 是一个位掩码,通过应用一个或者多个过滤器来确定要访问的节点。它的值在 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 中无用处。 |
NodeFilter.SHOW_ENTITYE | 显示实体节点,HTML 中无用处。 |
NodeFilter.SHOW_PROCESSING_INSTRUCTION | 显示处理指令节点,HTML 中无用处。 |
NodeFilter.SHOW_COMMENT | 显示注释节点。 |
NodeFilter.SHOW_DOCUMENT | 显示文档节点。 |
NodeFilter.SHOW_DOCUMENT_TYPE | 显示文档类型节点。 |
NodeFilter.SHOW_DOCUMENT_FRAGMENT | 显示文档片段节点,HTML 中无用处。 |
NodeFilter.SHOW_NOTATION | 显示符号节点,,HTML 中无用处。 |
除了 NodeFilter.SHOW_ALL
之外,它们都可以按位或操作符来组合多个选项,比如:
var whatToShow = odeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT;
可以使用 filter 参数来指定自定义的 NodeFilter 对象。每个 NodeFilter 对象有一个 acceptNode() 方法;如果应该访问某个节点,就返回 NodeFilter.FILTER_ACCEPT
;如果不应该访问,就返回 NodeFilter.FITLER_SKIP
。由于 NodeFilter 对象是抽象类型,所以只能创建一个包含 acceptNode() 方法的对象,然后将它传给 createNodeIterator()。
现在创建一个只显示 <p>
元素的节点迭代器:
var filter = {
acceptNode: function(node){
return node.tagName.toLowerCase() == "p" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FITLER_SKIP;
}
};
var iterator = document.createNodeIterator(root,NodeFilter.SHOW_ELEMENT,filter,false);
第三个参数也可以是一个与 acceptNode() 方法类似的函数:
var filter = function(node){
return node.tagName.toLowerCase() == "p" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FITLER_SKIP;
}
如果不指定过滤器,那么第三个参数传入 null。 下面创建一个能够访问所有类型节点的 NodeIterator:
var iterator = document.createNodeIterator(root,NodeFilter.SHOW_ALL,null,false);
NodeIterator 有两个方法:
- nextNode(),向前前进一步。
- previousNode(),向后后退一步。
刚刚创建的 NodeIterator 对象,有一个内部指针指向根节点,所以第一次调用 nextNode() 会返回根节点,当遍历到最后一个节点时,再调用 nextNode() 会返回 null。previousNode() 的机制与 nextNode() 类似。
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>遍历某个元素中的所有元素(加上过滤器)</title>
</head>
<body>
<div id="div1">
<p><b>Hello</b> world!</p>
<ul>
<li>List item 1</li>
<li>List item 2</li>
<li>List item 3</li>
</ul>
</div>
<script type="text/javascript">
var div = document.getElementById("div1");
var filter = function (node) {
return node.tagName.toLowerCase() == "li" ?
NodeFilter.FILTER_ACCEPT :
NodeFilter.FILTER_SKIP;
};
var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, filter, false);
var node = iterator.nextNode();
while (node !== null) {
console.log(node.tagName);//输出标签名
node = iterator.nextNode();
}
</script>
</body>
</html>
如果只想返回 <li>
元素,可以加一个过滤器:
var div = document.getElementById("div1");
var filter = function (node) {
return node.tagName.toLowerCase() == "li" ?
NodeFilter.FILTER_ACCEPT :
NodeFilter.FILTER_SKIP;
};
var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, filter, false);
var node = iterator.nextNode();
while (node !== null) {
console.log(node.tagName);//输出标签名
node = iterator.nextNode();
}
注意: Firefox 3.5 之前的版本没有实现 createNodeIterator() 方法!
2 TreeWalker
它除了包括 nextNode() 、previousNode() 之外,还包括这些方法:
方法名 | 说明 |
---|---|
parentNode() | 遍历到当前节点的父节点 |
firstChild() | 遍历到当前节点的第一个子节点 |
lastChild() | 遍历到当前节点的最后一个子节点 |
nextSibling() | 遍历到当前节点的下一个同辈节点 |
previsousSibling() | 遍历到当前节点的上一个同辈节点 |
使用 document.createTreeWalker() 创建 TreeWalker 对象,它接受的 4 个参数与 createNodeIterator() 方法相同:
var div = document.getElementById("div1");
var filter = function (node) {
return node.tagName.toLowerCase() == "li" ?
NodeFilter.FILTER_ACCEPT :
NodeFilter.FILTER_SKIP;
};
var iterator = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, filter, false);
var node = iterator.nextNode();
while (node !== null) {
console.log(node.tagName);//输出标签名
node = iterator.nextNode();
}
在 TreeWalker 中,还可以返回 NodeFilter.FILTER_REJECT
!它与 NodeFilter.FILTER_SKIP
不同之处是,NodeFilter.FILTER_SKIP
会跳过相应的节点,并继续执行到子树中的下一个节点,而 NodeFilter.FILTER_REJECT
会跳过相应的节点,并跳过这个节点的整个子树!
TreeWalker 能够在 DOM 结构中沿着任何方向移动,很厉害吧 O(∩_∩)O~。比如我们把之前的例子改造下,可以不用过滤器,就可以取得所有 <li>
元素:
var div = document.getElementById("div1");
var walker = document.createTreeWalker(div, 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.nextSibling();
}
TreeWalker 有一个叫做 currentNode 的属性,它表示的是,在上一次遍历中返回的节点。通过修改它也可以修改当前遍历继续执行的起点,比如:
var node = walker.nextNode();
console.log(node ==== walker.currentNode);//true
walker.currentNode = document.body;//修改了起点
因为 IE 没有对应的类型和方法,所以几乎没有跨浏览器的遍历方案!