JavaScript学习笔记(四十二)DOM2和DOM3(三)
遍历
- DOM2 Traversal and Range模块定义了两个类型用于辅助顺序遍历DOM结构。这两个类型NodeIterator和Treewalker从某个起点开始执行对DOM结构的深度优先遍历。
- DOM遍历是**对DOM结构的深度优先遍历,至少允许朝两个方向移动(取决于类型)**。遍历以给定节点为根,不能在 DOM中向上超越这个根节点。
- 查看以下HTML:DOM树如下:
1
2
3
4
5
6
7
8
9
<html>
<head>
<title>Example</title>
</head>
<body>
<p><b>Hello</b> world!</p>
</body>
</html>
1)NodeIterator
- NodeIterator类型是两个类型中比较简单的,可以通过document.createNodeIterator()方法创建其实例。方法接收四个参数:
- root:作为遍历根节点的节点;
- whatToShow:数值代码,表示应该访问哪些节点;
- filter:NodeFilter对象或函数,表示是否接收或跳过特定节点;
- entityReferenceExpansion,布尔值,表示是否扩展实体引用。这个参数在HTML文档中没有效果,因为实体引用永远不扩展。
- whatToshow参数是一个位掩码,通过应用一个或多个过滤器来指定访问哪些节点。这个参数对应的常量是在NodeFilter类型中定义的。
- 这些值除了 NodeFilter.SHOW_ALL 之外,都可以组合使用。
createNodeIterator()方法的filter参数可以用来指定自定义NodeFilter对象,或者一个作为节点过滤器的函数。
NodeFilter对象只有一个方法**acceptNode()**,如果给定节点应该访问就返回NodeFilter.FILTER_ACCEPT,否则返回NodeFilter.FILTER_SKIP。
NodeFilter 是一个抽象类型,所以不可能创建它的实例。只要创建一个包含 acceptNode()的对象,然后把它传给createNodeIterator()就可以了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/*
以下代码定义了只接收<p>元素的节点过滤器对象:
*/
let filter = {
acceptNode(node) {
return node.tagName.toLowerCase() == "p" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}
};
/*
filter 参数还可以是一个函数,与 acceptNode()的形式一样,如下面的例子所示:
*/
let filter = function(node) {
return node.tagName.toLowerCase() == "p" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
};
let iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, filter, false);1
2
3
4/*
创建一个简单的遍历所有节点的 NodeIterator
*/
let iterator = document.createNodeIterator(document, NodeFilter.SHOW_ALL, null, false);NodeIterator 的两个主要方法是nextNode()和 previousNode():
- nextNode()方法在DOM子树中以深度优先方式进前一步;previousNode()是在遍历中后退一步。
- NodeIterator对象的时候,会有一个内部指针指向根节点,因此第一次调用nextNode ()返回的是根节点。。
- 当遍历到达DOM树最后一个节点时,nextNode()返回null;previousNode()返回遍历的根节点后,再次调用也会返回null。
1
2
3
4
5
6
7
8<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>
假设想要遍历
<div>
元素内部所有元素。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16let div = document.getElementById("div1");
let iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, null, false);
let node = iterator.nextNode();
while (node !== null) {
console.log(node.tagName); // 输出标签名
node = iterator.nextNode();
}
/*
DIV
P
B
UL
LI
LI
LI
*/如果只想遍历
<li>
元素,可以传入一个过滤器:
1 | let div = document.getElementById("div1"); |
2)TreeWalker
- Treewalker是NodeIterator的高级版。
- 除了包含同样的nextNode().previousNode()方法,Treewalker还添加了如下在 DOM结构中向不同方向遍历的方法。
- parentNode(),遍历到当前节点的父节点;
- firstChild(),遍历到当前节点的第一个子节点;
- lastChild(),遍历到当前节点的最后一个子节点;
- nextSibling(),遍历到当前节点的下一个同胞节点;
- previoussibling(),遍历到当前节点的上一个同胞节点。
- 调用document.createTreewalker()方法来创建Treewalker对象。
- 参数: 作为遍历起点的根节点、要查看的节点类型、节点过滤器、一个表示是否扩展实体引用的布尔值。
- 节点过滤器:除了可以返回NodeFilter.FILTER_ACCEPT和NodeFilter.FILTER_SKIP,还可以返回NodeFilter.FILTER_REJECT。
- 在使用NodeIterator时,NodeFilter.FILTER_SKIP和NodeFilter.FILTER_REJECT是一样的。
- 在使用Treewalker时,NodeFilter.FILTER_SKIP表示跳过节点,访问子树中的下一个节点,而NodeFilter.FILTER_REJECT则表示跳过该节点以及该节点的整个子树。
范围
1、DOM范围
- DOM2在Document类型上定义了一个createRange()方法可用于创建一个DOM范围对象。
1 | let range = document.createRange(); |
- 新创建的氛围对象是与创建它的文档关联,不能在其他文档中使用。
- 创建范围并指定它的位置后就可以对范围的内容执行一些操作,从而实现对底层DOM树更精细的控制。
- 每个范围都是 Range类型的实例,拥有相应的属性和方法。
2、简单选择
- selectNode()或 selectNodeContents()是通过范围选择文档中౼个部分最简单的方式。
- 参数:接收一个节点,并将该节点的信息添加到调用它的氛围
- 不同:
- selectNode()选择整个节点,包括其后代节点。
- selectNodeContents()只选择节点的后代。
1
2
3
4
5
6
<html>
<body>
<p id="p1"><b>Hello</b> world!</p>
</body>
</html>1
2
3
4
5let range1 = document.createRange(),
range2 = document.createRange(),
p1 = document.getElementById("p1");
range1.selectNode(p1);
range2.selectNodeContents(p1);
- 调用selectNode()时,startContainer、endContainer和 commonAncestorContainer都等于传入节点的父节点。在这个例子中,这几个属性都等于document.body。
- 选定节点或节点后代之后,还可以在范围上调用相应的方法,实现对范围中选区的更精细控制:
3、复杂选择
- 要创建复杂的范围,需要使用setstart()和setEnd()方法。
- 两个方法都接收两个参数:参照节点和偏移量。
- 对setstart()来说,参照节点会成为startContainer,而偏移量会赋值给startoffset。
- 对setEnd()而言,参照节点会成为endContainer,而偏移量会赋值给endoffset。
1
2
3
4
5
6
<html>
<body>
<p id="p1"><b>Hello</b> world!</p>
</body>
</html>1
2
3
4
5
6
7
8
9/*
通过范围从中选择从"Hello"中的"1lo"到" world! 中的"o"的部分。
*/
let p1 = document.getElementById("p1"),
helloNode = p1.firstChild.firstChild,
worldNode = p1.lastChild
let range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
4、操作范围
- 创建范围之后,浏览器会在内部创建一个文档片段节点,用于包含范围选区中的节点。
- 为操作范围的内容,选区中的内容必须格式完好。
- 范围能够确定缺ܾ的开始和结束标签,从而可以重构出有效的 DOM 结构,以便后续操作。
1 |
|
针对以上例子,范围发现选区中缺少一个开始的
<b>
标签,于是会在后台动态补上这个标签,同时还需要补上封闭”He”的结束标签</b>
,结果会把 DOM 修改为这样:1
<p><b>He</b><b>llo</b> world!</p>
deleteContents()方法会从文档中删除范围包含的节点。
1 | let p1 = document.getElementById("p1"), |
- 执行上面的代码之后,页面中的 HTML 会变成这样:
1
<p><b>He</b>rld!</p>
- extractContents()也会从文档中移除范围选区,同时返回范围对应的文档片段。
- 下面例子中提取了范围的文档片段,然后把它添加到文档
<body>
元素的最后。1
2
3
4
5
6
7
8let p1 = document.getElementById("p1"),
helloNode = p1.firstChild.firstChild,
worldNode = p1.lastChild,
range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
let fragment = range.extractContents();
p1.parentNode.appendChild(fragment);1
2<p><b>He</b>rld!</p>
<b>llo</b> wo - 如果不想把范围从文档中移除,也可以使用cloneContents()创建一个副本,然后把这个副本插入到文档其他地方。
5、范围插入
- insertNode():方法可以在范围选区的开始位置插人一个节点。
1
<p id="p1"><b>Hello</b> world!</p>
1
2
3
4
5
6
7
8
9
10let p1 = document.getElementById("p1"),
helloNode = p1.firstChild.firstChild,
worldNode = p1.lastChild,
range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
let span = document.createElement("span");
span.style.color = "red";
span.appendChild(document.createTextNode("Inserted text"));
range.insertNode(span);1
<p id="p1"><b>He<span style="color: red">Inserted text</span>llo</b> world</p>
- surroundContents():方法插入包含范围的内容。
- 接收一个参数:包含范围内容的节点。
6、范围折叠
- 如果范围并没有选择文档的任何部分,则称为折叠( collapsed )。
- 折叠范围有点类似文本框:如果文本框中有文本,那么可以用鼠标选中以高亮显示全部文本。这时候,如果再单击鼠标,则选区会被移除,光标会落在某两个字符中间。
- 在折叠范围时,位置会被设置为范围与文档交界的地方,可能是范围选区的开始处,也可能是结尾处。
- collapse()方法
- 接收一个参数:布尔值,表示折叠到范围哪一段。
- true表示折叠到起点;false表示折叠到终点。
1
2
3
4
5
6let p1 = document.getElementById("p1"),
p2 = document.getElementById("p2"),
range = document.createRange();
range.setStartAfter(p1);
range.setStartBefore(p2);
console.log(range.collapsed); // true
7、范围比较
- compareBoundaryPoints()方法确定范围之间是否存在公共的边界。
- 参数:接收两个参数,要比较的范围和以恶常量值,表示比较的方式。
- Range.START_TO_START(0),比较两个范围的起点;
- Range.START_TO_END(1),比较第一个范围的起点和第二个范围的终点;
- Range.END_TO_END(2),比较两个范围的终点;
- Range.END_TO_START (3),比较第一个范围的终点和第二个范围的起点。
- 返回值:
- 在第一个范围的边界点位于第二个范围的边界点之前时返回-1;
- 在两个范围的边界点相等时返回0;
- 在第一个范围的边界点位于第二个范围的边界点之后时返回1。
1
<p id="p1"><b>Hello</b> world!</p>
1
2
3
4
5
6
7
8let range1 = document.createRange();
let range2 = document.createRange();
let p1 = document.getElementById("p1");
range1.selectNodeContents(p1);
range2.selectNodeContents(p1);
range2.setEndBefore(p1.lastChild);
console.log(range1.compareBoundaryPoints(Range.START_TO_START, range2)); // 0
console.log(range1.compareBoundaryPoints(Range.END_TO_END, range2)); // 1
- 参数:接收两个参数,要比较的范围和以恶常量值,表示比较的方式。
8、复制范围
- cloneRange()方法可以复制范围,会创建调用它的范围的副本。
- 新范围包含与原始范围一样的属性,修改其边界点不会影响原始范围。
9、清理
- detach()方法把范围从创建它的文档中剥离。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 姚永坤的小窝!
评论