
2025-02-26 日报 Day109

今日的鸡汤
囿于市井,面向山海,
远隔巷弄,且看花开。
今日学习内容
1、JS 红皮书 P476-489 第十六章:DOM2和DOM3
今日笔记
1、遍历: DOM2 Traversal and Range 模块定义了两个类型用于辅助顺序遍历 DOM 结构。这两个类型—— NodeIterator 和 TreeWalker——从某个起点开始执行对 DOM 结构的深度优先遍历。
- NodeIterator: NodeIterator 类型是两个类型中比较简单的,可以通过 document.createNodeIterator()方法创建其实例。这个方法接收以下 4 个参数。
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_ENTITY,实体节点。不是在 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 之外,都可以组合使用。比如,可以像下面这样使用按位或
操作组合多个选项:
let whatToShow = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT;
createNodeIterator()方法的 filter 参数可以用来指定自定义 NodeFilter 对象,或者一个作为节点过滤器的函数。NodeFilter 对象只有一个方法 acceptNode(),如果给定节点应该访问就返回 NodeFilter.FILTER_ACCEPT,否则返回 NodeFilter.FILTER_SKIP。因为 NodeFilter 是一个抽象类型,所以不可能创建它的实例。只要创建一个包含 acceptNode()的对象,然后把它传给createNodeIterator()就可以了。以下代码定义了只接收元素的节点过滤器对象:
NodeIterator 的两个主要方法是 nextNode()和 previousNode()。nextNode()方法在 DOM子树中以深度优先方式进前一步,而 previousNode()则是在遍历中后退一步。创建 NodeIterator对象的时候,会有一个内部指针指向根节点,因此第一次调用 nextNode()返回的是根节点。当遍历到达 DOM 树最后一个节点时,nextNode()返回 null。previousNode()方法也是类似的。当遍历到达DOM 树最后一个节点时,调用 previousNode()返回遍历的根节点后,再次调用也会返回 null。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19let filter = {
acceptNode(node) {
return node.tagName.toLowerCase() == "p" ?
NodeFilter.FILTER_ACCEPT :
NodeFilter.FILTER_SKIP;
}
};
let iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, filter, false);
// 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);
// 要创建一个简单的遍历所有节点的 NodeIterator,可以使用以下代码:
let iterator = document.createNodeIterator(root, NodeFilter.SHOW_ALL, null, false);假设想要遍历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>元素内部的所有元素,那么可以使用如下代码:这个例子中第一次调用 nextNode()返回1
2
3
4
5
6
7let 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();
}元素。因为 nextNode()在遍历到达 DOM 子树末尾时返回 null,所以这里通过 while 循环检测每次调用 nextNode()的返回值是不是 null。以上代码执行后会输出以下标签名:
DIV
P
B
UL
LI
LI
LI
如果只想遍历- 元素,可以传入一个过滤器,比如:
nextNode()和 previousNode()方法共同维护 NodeIterator 对 DOM 结构的内部指针,因此修改 DOM 结构也会体现在遍历中。1
2
3
4
5
6
7
8
9
10
11
12
13let div = document.getElementById("div1");
let filter = function(node) {
return node.tagName.toLowerCase() == "li" ?
NodeFilter.FILTER_ACCEPT :
NodeFilter.FILTER_SKIP;
};
let iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT,
filter, false);
let node = iterator.nextNode();
while (node !== null) {
console.log(node.tagName); // 输出标签名
node = iterator.nextNode();
}
2、TreeWalker: TreeWalker 是 NodeIterator 的高级版。除了包含同样的 nextNode()、previousNode()方法,TreeWalker 还添加了如下在 DOM 结构中向不同方向遍历的方法。
parentNode(),遍历到当前节点的父节点。
firstChild(),遍历到当前节点的第一个子节点。
lastChild(),遍历到当前节点的最后一个子节点。
nextSibling(),遍历到当前节点的下一个同胞节点。
previousSibling(),遍历到当前节点的上一个同胞节点。
TreeWalker 对象要调用 document.createTreeWalker()方法来创建,这个方法接收与document.createNodeIterator()同样的参数:作为遍历起点的根节点、要查看的节点类型、节点过滤器和一个表示是否扩展实体引用的布尔值。因为两者很类似,所以 TreeWalker 通常可以取代NodeIterator,比如:不同的是,节点过滤器(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_REJECT(而不是 NodeFilter.FILTER_SKIP),则会导致遍历立即返回,不会访问任何节点。这是因为第一个返回的元素是1
2
3
4
5
6
7
8
9
10
11
12
13let div = document.getElementById("div1");
let filter = function(node) {
return node.tagName.toLowerCase() == "li" ?
NodeFilter.FILTER_ACCEPT :
NodeFilter.FILTER_SKIP;
};
let walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT,
filter, false);
let node = iterator.nextNode();
while (node !== null) {
console.log(node.tagName); // 输出标签名
node = iterator.nextNode();
},其中标签名不是”li”,因此过滤函数返回 NodeFilter.FILTER_ REJECT,表示要跳过整个子树。因为本身就是遍历的根节点,所以遍历会就此结束。
当然,TreeWalker 真正的威力是可以在 DOM 结构中四处游走。如果不使用过滤器,单纯使用TreeWalker 的漫游能力同样可以在 DOM 树中访问- 元素,比如:
TreeWalker 类型也有一个名为 currentNode 的属性,表示遍历过程中上一次返回的节点(无论使用的是哪个遍历方法)。可以通过修改这个属性来影响接下来遍历的起点,如下面的例子所示:1
2
3
4
5
6
7
8
9let div = document.getElementById("div1");
let walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, null, false);
walker.firstChild(); // 前往<p>
walker.nextSibling(); // 前往<ul>
let node = walker.firstChild(); // 前往第一个<li>
while (node !== null) {
console.log(node.tagName);
node = walker.nextSibling();
}3、范围: 为了支持对页面更细致的控制,DOM2 Traversal and Range 模块定义了范围接口。范围可用于在文档中选择内容,而不用考虑节点之间的界限。(选择在后台发生,用户是看不到的。)范围在常规 DOM操作的粒度不够时可以发挥作用。1
2
3let node = walker.nextNode();
console.log(node === walker.currentNode); // true
walker.currentNode = document.body; // 修改起点- DOM范围: DOM2 在 Document 类型上定义了一个 createRange()方法,暴露在 document 对象上。使用这个方法可以创建一个 DOM 范围对象,如下所示:
与节点类似,这个新创建的范围对象是与创建它的文档关联的,不能在其他文档中使用。然后可以使用这个范围在后台选择文档特定的部分。创建范围并指定它的位置之后,可以对范围的内容执行一些操作,从而实现对底层 DOM 树更精细的控制。1
let range = document.createRange();
每个范围都是 Range 类型的实例,拥有相应的属性和方法。下面的属性提供了与范围在文档中位置相关的信息。
startContainer,范围起点所在的节点(选区中第一个子节点的父节点)。
startOffset,范围起点在 startContainer 中的偏移量。如果 startContainer 是文本节点、注释节点或 CData 区块节点,则 startOffset 指范围起点之前跳过的字符数;否则,表示范围中第一个节点的索引。
endContainer,范围终点所在的节点(选区中最后一个子节点的父节点)。
endOffset,范围起点在 startContainer 中的偏移量(与 startOffset 中偏移量的含义相同)。
commonAncestorContainer,文档中以startContainer和endContainer为后代的最深的节点。- 属性选择: 通过范围选择文档中某个部分最简单的方式,就是使用 selectNode()或 selectNodeContents()方法。这两个方法都接收一个节点作为参数,并将该节点的信息添加到调用它的范围。selectNode()方法选择整个节点,包括其后代节点,而 selectNodeContents()只选择节点的后代。假设有如下 HTML:
1
2
3
4
5
6
<html>
<body>
<p id="p1"><b>Hello</b> world!</p>
</body>
</html>例子中的这两个范围包含文档的不同部分。range1 包含1
2
3
4
5let range1 = document.createRange(),
range2 = document.createRange(),
p1 = document.getElementById("p1");
range1.selectNode(p1);
range2.selectNodeContents(p1);元素及其所有后代,而 range2 包含元素、文本节点”Hello”和文本节点” world!”.
4、复杂选择: 要创建复杂的范围,需要使用 setStart()和 setEnd()方法。这两个方法都接收两个参数:参照节点和偏移量。对 setStart()来说,参照节点会成为 startContainer,而偏移量会赋值给 startOffset。对 setEnd()而言,参照节点会成为 endContainer,而偏移量会赋值给 endOffset。
5、清理: 在使用完范围之后,最好调用 detach()方法把范围从创建它的文档中剥离。调用 detach()之后,就可以放心解除对范围的引用,以便垃圾回收程序释放它所占用的内存。下面是一个例子:6、小结: DOM2 Style 模块定义了如何操作元素的样式信息。1
2range.detach(); // 从文档中剥离范围
range = null; // 解除引用
每个元素都有一个关联的 style 对象,可用于确定和修改元素特定的样式。
要确定元素的计算样式,包括应用到元素身上的所有 CSS规则,可以使用getComputedStyle()方法。
通过 document.styleSheets 集合可以访问文档上所有的样式表。DOM2 Traversal and Range 模块定义了与 DOM 结构交互的不同方式。
NodeIterator 和 TreeWalker 可以对 DOM 树执行深度优先的遍历。
NodeIterator 接口很简单,每次只能向前和向后移动一步。TreeWalker 除了支持同样的行为,还支持在 DOM 结构的所有方向移动,包括父节点、同胞节点和子节点。
范围是选择 DOM 结构中特定部分并进行操作的一种方式。
通过范围的选区可以在保持文档结构完好的同时从文档中移除内容,也可复制文档中相应的部分。此页目录2025-02-26 日报 Day109 - 元素,可以传入一个过滤器,比如: