第十二章:DOM2 和 DOM3(遍历)
2017-05-04 15:31
381 查看
DOM2 和 DOM3
遍历
NodeIterator
TreeWalker
root:搜索起点
whatToShow:表示要访问哪些类型节点的数字代码
filter:是一个NodeFilter对象,或者一个表示过滤哪些特殊节点的函数。
entityReferenceExpansion:布尔值,表示是否要扩展实体引用。这个参数在HTML中没有用,因为其中的实体引用不能扩展。
其中whatToShow是一个位掩码,通过应用一或多个过滤器来确定要访问哪些节点。这个参数的值以常量形式在NodeFilter(IE8-不支持)类型中定义,如下所示:
一般我们不会在意具体的值是多少,比如我们只显示元素和文本节点,可以使用:
filter参数可以是一个NodeFilter对象,也可以是一个函数。如果是前者,每个NodeFilter对象只有一个一个方法,即acceptNode(),我们需要设置这个方法。该方法有3个返回值(可以查看我上面的快捷链接的介绍):FILTER_ACCEPT、FILTER_REJECT和FILTER_SKIP(他们的值分别是1、2、3)。对于createNodeIterator()来说,如果应该返回给定的节点,则acceptNode()需要返回FILTER_ACCEPT否则就返回FILTER_SKIP(也可以使用FILTER_REJECT,效果一样)。见下面的例子:
NodeIterator类型的主要两个方法是nextNode()和previousNode()。看看名字就知道是什么用处,直接上例子:
虽然说挺强大的,但是我觉得用处可能没有那么广,因为我们通常只会遍历一层子节点,所以通过childNodes,再通过nextSibling和previousSibling可能会更实用一点。这个方法更多用到’深入骨髓’的那种遍历,比如说我要获取文档下的a节点和div节点的集合。虽然他们各自可以通过getElementsByTagName获得,但是要按照顺序获得他们的集合,就没有那么容易写了,下面是我想到的最普通的深度优先遍历的写法。
parentNode():遍历到当前节点的父节点。
firstChild():遍历到当前节点的第一个子节点。
lastChild():遍历到当前节点的最后一个子节点。
nextSibling():遍历到当前节点的下一个兄弟节点。
previousSibling():遍历到当前节点的上一个兄弟节点。
创建TreeWalker对象要使用document.createTreeWalker()方法,这个方法同样接收4个参数。用法和document.createNodeIterator()类似。还记得前面说到的filter参数的返回值吗?前面只提到了FILTER_ACCEPT和FILTER_SKIP,至于FILTER_REJECT的用法在createNodeIterator()中与FILTER_SKIP相同,但是在createTreeWalker()中,则会跳过相应节点及该节点的整个子树。
TreeWalker类型还有一个属性,名叫currentNode。顾名思义,表示任何遍历方法在上一次遍历中返回的节点。通过设置这个属性也可以修改遍历继续进行的起点,例子如下:
这种修改遍历起点的能力引起了我的兴趣。假设我修改的节点并不是一开始root的子节点,那么是否还会遍历修改后的节点的兄弟节点?如果是子节点又会如何?接下来做一个实验:
通过结果可以看出,即使一开始遍历的根节点是test1,在修改了currentNode后,会认为是从当前文档下已经遍历到修改后的节点,继续遍历会认为是当前文档下遍历的延续。不过这只是我的猜测,我又进行了下面的测试:
这个例子在遍历到test111节点后,立马将当前节点改为test2,且向前遍历,这个结果就很有意思了。按照我的猜测他应该会把father节点也一同遍历到,但结果并不是如此,难道只会遍历到第一个兄弟节点?
后来我修改了一下函数,从test2开始遍历,搜素到test22就将test3设为当前节点,结果显示,最终只会向前遍历到test2节点。可见向前遍历只会遍历到一开始设置的root节点。那就有意思了,我在想如果我最开始的例子是先遍历test3再跳到test2向前或向后遍历,结果是不是截然相反?
结果真如我所想,不仅连father输出来了,就连body head html都出来了(只不过没有id没有打印)。我再做一个向后的遍历看是不是到test3就结束了:
结果也的确到test32就终止了。那我总结一下是这样的:当改变currentNode后遍历又遇到了之前createTreeWalker的root节点,则又会像一开始一样进行工作,否则就会以document为根节点反映一些结果。不过我的这个总结还需要靠一个例子去证明。因为之前只用到了nextNode()和previousNode()。而没有用他的其他方法。所以我决定再做几个实验:
检验nextSibling()(previousSibling()类似):设置test0 1 2 3一共4个兄弟节点,一开始用test2创建walker。遍历完test2跳到test0节点,然后去搜索nextSibling(),看是否能搜索到test3
检验parentNode():设置test0 1 2 3一共4个兄弟节点,一开始用test2创建walker。遍历完test2跳到test0节点,遍历完test0后回到test21请求parentNode()看是否会搜索到father
代码我就不给了,第一个结果的确是到test2就终止了。第二个的结果也是如我所愿遍历到test2也终止了。可见我的猜想的确是正确的。不过前面还忘记说一件事。如果修改当前节点仍然是一开始指定的root的子节点,那么除了改变当前节点位置外对遍历结果不会有任何影响。这一点从刚才的实验也可以反映。因为你哪怕改到外面的节点去了,最终要是又回到了一开始设置的节点集里,又会回归“正常”。
遍历
NodeIterator
TreeWalker
DOM2 和 DOM3
遍历
DOM Level 2 Tranversal and Range 模块定义了2个用于辅助完成顺序遍历DOM结构的类型:NodeIterator和TreeWalker。这两个类型能够基于给定的起点对DOM结构执行深度优先的遍历操作。IE不支持DOM遍历(然而IE9+却支持,我遇到书中好多地方说IE不支持,难道这部分的知识的编写时期还没有出来IE9?)。使用下面代码可以检测浏览器对DOM2 级遍历能力的支持情况:var supportsTraversals = document.implementation.hasFeature("Traversal", "2.0"); var supportsNodeIterator = (typeof document.createNodeIterator == "function"); var supportsTreeWalker = (typeof document.createTreeWalker == "function"); alert(supportsTraversals);//IE 8- false alert(supportsNodeIterator);//IE 8- false alert(supportsTreeWalker);//IE 8- false
NodeIterator
可以使用document.createNodeIterator()方法创建新实例,这个方法接收4个参数:root:搜索起点
whatToShow:表示要访问哪些类型节点的数字代码
filter:是一个NodeFilter对象,或者一个表示过滤哪些特殊节点的函数。
entityReferenceExpansion:布尔值,表示是否要扩展实体引用。这个参数在HTML中没有用,因为其中的实体引用不能扩展。
其中whatToShow是一个位掩码,通过应用一或多个过滤器来确定要访问哪些节点。这个参数的值以常量形式在NodeFilter(IE8-不支持)类型中定义,如下所示:
访问方式 | 值 | 描述 |
---|---|---|
NodeFilter.SHOW_ALL | 4294967295(32个1) | 显示所有类型的节点 |
NodeFilter.SHOW_ELEMENT | 1(第一个1) | 显示元素节点 |
NodeFilter.SHOW_ATTRIBUTE | 2(第二个个1) | 显示特性节点,由于DOM结构原因,实际上不能使用这个值,用了也没效果。 |
NodeFilter.SHOW_TEXT | 4 | 显示文本节点 |
NodeFilter.SHOW_CDATA_SECTION | 8 | 显示CDATA节点。对HTML无效(因为没有这样的节点,在XML中存在) |
NodeFilter.SHOW_ENTITY_REFERENCE | 16 | 显示实体引用节点。对HTML无效 |
NodeFilter.SHOW_ENTITY | 32 | 显示实体节点。对HTML无效 |
NodeFilter.SHOW_PROCESSING_INSTRUCTION | 64 | 显示处理指令节点。对HTML无效 |
NodeFilter.SHOW_COMMENT | 128 | 显示注释节点 |
NodeFilter.SHOW_DOCUMENT | 256 | 显示文档节点 |
NodeFilter.SHOW_DOCUMENT_TYPE | 512 | 显示文档类型节点 |
NodeFilter.SHOW_DOCUMENT_FRAGMENT | 1024 | 显示文档片段节点。对HTML无效 |
NodeFilter.SHOW_NOTATION | 2048 | 显示符号节点。对HTML无效 |
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT作为第二个参数。另外可以发现每个参量代表的具体的值是
2^(nodeType - 1)。
console.log(NodeFilter.SHOW_ALL);//4294967295 32个1 console.log(NodeFilter.SHOW_ELEMENT);//1 2^(1-1) console.log(NodeFilter.SHOW_ATTRIBUTE);//2 2^(2-1) console.log(NodeFilter.SHOW_TEXT);//4 2^(3-1) console.log(NodeFilter.SHOW_CDATA_SECTION);//8 2^(4-1) console.log(NodeFilter.SHOW_ENTITY_REFERENCE);//16 2^(5-1) console.log(NodeFilter.SHOW_ENTITY);//32 2^(6-1) console.log(NodeFilter.SHOW_PROCESSING_INSTRUCTION);//64 console.log(NodeFilter.SHOW_COMMENT);//128 console.log(NodeFilter.SHOW_DOCUMENT);//256 console.log(NodeFilter.SHOW_DOCUMENT_TYPE);//512 console.log(NodeFilter.SHOW_DOCUMENT_FRAGMENT);//1024 console.log(NodeFilter.SHOW_NOTATION);//2048 2^(12-1)
filter参数可以是一个NodeFilter对象,也可以是一个函数。如果是前者,每个NodeFilter对象只有一个一个方法,即acceptNode(),我们需要设置这个方法。该方法有3个返回值(可以查看我上面的快捷链接的介绍):FILTER_ACCEPT、FILTER_REJECT和FILTER_SKIP(他们的值分别是1、2、3)。对于createNodeIterator()来说,如果应该返回给定的节点,则acceptNode()需要返回FILTER_ACCEPT否则就返回FILTER_SKIP(也可以使用FILTER_REJECT,效果一样)。见下面的例子:
var filter = { acceptNode: function (node) { return node.tagName.toLowerCase() == "p" ? NodeFilter.FILTER_ACCEPT: NodeFilter.FILTER_SKIP; } }; //几乎没有区别,也需要返回NodeFilter.FILTER_ACCEPT或者NodeFilter.FILTER_SKIP var filter2 = function (node) { return node.tagName.toLowerCase() == "p" ? NodeFilter.FILTER_ACCEPT: NodeFilter.FILTER_SKIP; }
NodeIterator类型的主要两个方法是nextNode()和previousNode()。看看名字就知道是什么用处,直接上例子:
<!DOCTYPE html> <html> <head> <title>NodeIterator Example</title> <script type="text/javascript"> var filter = function (node) { return node.tagName.toLowerCase() == "li"? NodeFilter.FILTER_ACCEPT: NodeFilter.FILTER_SKIP; }; function makeList() { var div = document.getElementById("div1"); var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, filter, false); var output = document.getElementById("text1"); output.value = ""; //这种获取行为是动态的,nextNode()只会返回最新状态,而不是createNodeIterator时的状态 var node = iterator.nextNode(); while (node !== null) { output.value += node.tagName + "\n"; node = iterator.nextNode(); } } </script> </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> <textarea rows="10" cols="40" id="text1"></textarea><br /> <input type="button" value="Make List" onclick="makeList()" /> <input type="button" value="changeFilter" onclick="filter = null;" /> </body> </html>
虽然说挺强大的,但是我觉得用处可能没有那么广,因为我们通常只会遍历一层子节点,所以通过childNodes,再通过nextSibling和previousSibling可能会更实用一点。这个方法更多用到’深入骨髓’的那种遍历,比如说我要获取文档下的a节点和div节点的集合。虽然他们各自可以通过getElementsByTagName获得,但是要按照顺序获得他们的集合,就没有那么容易写了,下面是我想到的最普通的深度优先遍历的写法。
<html> <body> <div id="test"> <a href="#"></a> <div> <a href="#"></a> </div> <h1></h1> <h2></h2> <div> <div> <a href="#"></a> </div> <a href="#"></a> </div> </div> <input type="button" value="test" onclick="console.log(getElements(document.getElementById('test'),filter));"> <script> function filter(node) { //我随便写了一个过滤器。 var name = node.tagName.toLowerCase(); return name == "a" || name == "div"; } //自定义的一种遍历,我决定用递归去实现。 function getElements(root, filter) { var arr = [];//用数组保存,说明我这个方法的缺点是非动态的。 if (root) { if (filter(root)) { arr.push(root); } var children = root.children;//这里用children不用childNodes是为了过滤文本节点。只是测试 for (var i=0, len=children.length; i<len; i++) { arr.push.apply(arr, getElements(children[i], filter)); } } return arr; } </script> </body> </html>
TreeWalker
TreeWalker 是NodeIterator的一个更高级的版本。除了包括nextNode()和previousNode()在内的相同功能外,这个类型还提供了用于不同方向上遍历DOM结构的方法。parentNode():遍历到当前节点的父节点。
firstChild():遍历到当前节点的第一个子节点。
lastChild():遍历到当前节点的最后一个子节点。
nextSibling():遍历到当前节点的下一个兄弟节点。
previousSibling():遍历到当前节点的上一个兄弟节点。
创建TreeWalker对象要使用document.createTreeWalker()方法,这个方法同样接收4个参数。用法和document.createNodeIterator()类似。还记得前面说到的filter参数的返回值吗?前面只提到了FILTER_ACCEPT和FILTER_SKIP,至于FILTER_REJECT的用法在createNodeIterator()中与FILTER_SKIP相同,但是在createTreeWalker()中,则会跳过相应节点及该节点的整个子树。
TreeWalker类型还有一个属性,名叫currentNode。顾名思义,表示任何遍历方法在上一次遍历中返回的节点。通过设置这个属性也可以修改遍历继续进行的起点,例子如下:
var node = walker.nextNode(); alert(node === walker.currentNode);//true walker.currentNode = document.body;//修改起点
这种修改遍历起点的能力引起了我的兴趣。假设我修改的节点并不是一开始root的子节点,那么是否还会遍历修改后的节点的兄弟节点?如果是子节点又会如何?接下来做一个实验:
<html> <body> <div id="father"> <div id="test1"> <div id="test11"> <div id="test111"></div> <div id="test112"></div> </div> <div id="test12"></div> </div> <div id="test2"> <div id="test21"></div> <div id="test22"></div> </div> <div id="test3"> <div id="test31"></div> <div id="test32"></div> </div> </div> <div id="brother"></div> <script> var node = document.getElementById("test1"); var walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT); var currentNode = walker.nextNode(); while (currentNode != null) { if (currentNode.tagName.toLowerCase() == "script") { console.log("script!"); } else { console.log(currentNode.id); } if (currentNode.id == "test111") { walker.currentNode = document.getElementById("test2"); currentNode = walker.currentNode; continue; } currentNode = walker.nextNode(); } </script> </body> </html> 运行结果如下: ----------------------------------- test11 test111 test2 test21 test22 test3 test31 test32 brother script!
通过结果可以看出,即使一开始遍历的根节点是test1,在修改了currentNode后,会认为是从当前文档下已经遍历到修改后的节点,继续遍历会认为是当前文档下遍历的延续。不过这只是我的猜测,我又进行了下面的测试:
<html> <body edee > <div id="father"> <div id="test1"> <div id="test11"> <div id="test111"></div> <div id="test112"></div> </div> <div id="test12"></div> </div> <div id="test2"> <div id="test21"></div> <div id="test22"></div> </div> <div id="test3"> <div id="test31"></div> <div id="test32"></div> </div> </div> <div id="brother"></div> <script> var node = document.getElementById("test1"); var walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT); var currentNode = walker.currentNode;//即root节点 var flag = true; while (currentNode != null) { if (currentNode.tagName.toLowerCase() == "script") { console.log("script!"); } else { console.log(currentNode.id); } if (currentNode.id == "test111" && flag) { walker.currentNode = document.getElementById("test2"); currentNode = walker.currentNode; flag = false; continue; } if (flag) currentNode = walker.nextNode(); else currentNode = walker.previousNode(); } </script> </body> </html> 结果如下: -------------------------------------- test1 test11 test111 test2 test12 test112 test111 test11 test1
这个例子在遍历到test111节点后,立马将当前节点改为test2,且向前遍历,这个结果就很有意思了。按照我的猜测他应该会把father节点也一同遍历到,但结果并不是如此,难道只会遍历到第一个兄弟节点?
<div id="father"> <div id="test0"> <div id="test01"></div> <div id="test02"></div> </div> <div id="test1"> <div id="test11"> <div id="test111"></div> <div id="test112"></div> </div> <div id="test12"></div> </div> <div id="test2"> <div id="test21"></div> <div id="test22"></div> </div> <div id="test3"> <div id="test31"></div> <div id="test32"></div> </div> </div> 但是结果没有变化。。。 ----------------------------------- test1 test11 test111 test2 test12 test112 test111 test11 test1
后来我修改了一下函数,从test2开始遍历,搜素到test22就将test3设为当前节点,结果显示,最终只会向前遍历到test2节点。可见向前遍历只会遍历到一开始设置的root节点。那就有意思了,我在想如果我最开始的例子是先遍历test3再跳到test2向前或向后遍历,结果是不是截然相反?
<html> <body> <div id="father"> <div id="test0"> <div id="test01"></div> <div id="test02"></div> </div> <div id="test1"> <div id="test11"> <div id="test111"></div> <div id="test112"></div> </div> <div id="test12"></div> </div> <div id="test2"> <div id="test21"></div> <div id="test22"></div> </div> <div id="test3"> <div id="test31"></div> <div id="test32"></div> </div> </div> <div id="brother"></div> <script> var node = document.getElementById("test3"); var walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT); var currentNode = walker.currentNode;//即root节点 var flag = true; while (currentNode != null) { if (currentNode.tagName.toLowerCase() == "script") { console.log("script!"); } else { console.log(currentNode.id); } if (currentNode.id == "test32" && flag) { walker.currentNode = document.getElementById("test2"); currentNode = walker.currentNode; flag = false; continue; } if (flag) currentNode = walker.nextNode(); else currentNode = walker.previousNode(); } </script> </body> </html>
结果真如我所想,不仅连father输出来了,就连body head html都出来了(只不过没有id没有打印)。我再做一个向后的遍历看是不是到test3就结束了:
var node = document.getElementById("test3"); var walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT); var currentNode = walker.currentNode;//即root节点 var flag = true; while (currentNode != null) { if (currentNode.tagName.toLowerCase() == "script") { console.log("script!"); } else { console.log(currentNode.id); } if (currentNode.id == "test32" && flag) { walker.currentNode = document.getElementById("test2"); currentNode = walker.currentNode; flag = false; continue; } currentNode = walker.nextNode(); }
结果也的确到test32就终止了。那我总结一下是这样的:当改变currentNode后遍历又遇到了之前createTreeWalker的root节点,则又会像一开始一样进行工作,否则就会以document为根节点反映一些结果。不过我的这个总结还需要靠一个例子去证明。因为之前只用到了nextNode()和previousNode()。而没有用他的其他方法。所以我决定再做几个实验:
检验nextSibling()(previousSibling()类似):设置test0 1 2 3一共4个兄弟节点,一开始用test2创建walker。遍历完test2跳到test0节点,然后去搜索nextSibling(),看是否能搜索到test3
检验parentNode():设置test0 1 2 3一共4个兄弟节点,一开始用test2创建walker。遍历完test2跳到test0节点,遍历完test0后回到test21请求parentNode()看是否会搜索到father
代码我就不给了,第一个结果的确是到test2就终止了。第二个的结果也是如我所愿遍历到test2也终止了。可见我的猜想的确是正确的。不过前面还忘记说一件事。如果修改当前节点仍然是一开始指定的root的子节点,那么除了改变当前节点位置外对遍历结果不会有任何影响。这一点从刚才的实验也可以反映。因为你哪怕改到外面的节点去了,最终要是又回到了一开始设置的节点集里,又会回归“正常”。
相关文章推荐
- 《JS高程(3)》DOM2和DOM3-遍历-第12章笔记(24)
- 第十二章:DOM2 和 DOM3(样式)
- DOM扩展_元素遍历
- jQueryDom遍历方法
- jQuery基础教程之DOM操作-遍历节点-prev()方法
- jQuery基础教程之DOM操作-遍历节点-find()方法
- 不要遍历dom
- JavaScript遍历DOM
- DOM2节点的遍历
- JQuery 遍历对象(DOM,数组,JSON)方法
- jQuery 遍历DOM
- Jquery中的DOM操作 (八.遍历节点)
- JQuery_dom属性操作、节点遍历及包裹
- 全栈JavaScript之路( 二十四 )DOM2、DOM3, 不涉及XML命名空间的扩展
- jQuery基础教程--DOM遍历方法
- 遍历DOM精简实现
- PHP DOM操作XML文档,查询遍历结点元素
- jQuery八、DOM遍历
- 遍历dom时如何优化JS性能
- DOM对象内的元素属性遍历