2025-02-26 日报 Day109

2025-02-26 日报 Day109

Yuyang 前端小白🥬

今日的鸡汤

囿于市井,面向山海,
远隔巷弄,且看花开。

今日学习内容

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()就可以了。以下代码定义了只接收

    元素的节点过滤器对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    let 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);
    NodeIterator 的两个主要方法是 nextNode()和 previousNode()。nextNode()方法在 DOM子树中以深度优先方式进前一步,而 previousNode()则是在遍历中后退一步。创建 NodeIterator对象的时候,会有一个内部指针指向根节点,因此第一次调用 nextNode()返回的是根节点。当遍历到达 DOM 树最后一个节点时,nextNode()返回 null。previousNode()方法也是类似的。当遍历到达DOM 树最后一个节点时,调用 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>
    假设想要遍历
    元素内部的所有元素,那么可以使用如下代码:
    1
    2
    3
    4
    5
    6
    7
    let 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()返回
    元素。因为 nextNode()在遍历到达 DOM 子树末尾时返回 null,所以这里通过 while 循环检测每次调用 nextNode()的返回值是不是 null。以上代码执行后会输出以下标签名:
    DIV
    P
    B
    UL
    LI
    LI
    LI
    如果只想遍历
  • 元素,可以传入一个过滤器,比如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let 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();
    }
    nextNode()和 previousNode()方法共同维护 NodeIterator 对 DOM 结构的内部指针,因此修改 DOM 结构也会体现在遍历中。
    2、TreeWalker: TreeWalker 是 NodeIterator 的高级版。除了包含同样的 nextNode()、previousNode()方法,TreeWalker 还添加了如下在 DOM 结构中向不同方向遍历的方法。
     parentNode(),遍历到当前节点的父节点。
     firstChild(),遍历到当前节点的第一个子节点。
     lastChild(),遍历到当前节点的最后一个子节点。
     nextSibling(),遍历到当前节点的下一个同胞节点。
     previousSibling(),遍历到当前节点的上一个同胞节点。
    TreeWalker 对象要调用 document.createTreeWalker()方法来创建,这个方法接收与document.createNodeIterator()同样的参数:作为遍历起点的根节点、要查看的节点类型、节点过滤器和一个表示是否扩展实体引用的布尔值。因为两者很类似,所以 TreeWalker 通常可以取代NodeIterator,比如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let 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();
    }
    不同的是,节点过滤器(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),则会导致遍历立即返回,不会访问任何节点。这是因为第一个返回的元素是
    ,其中标签名不是”li”,因此过滤函数返回 NodeFilter.FILTER_ REJECT,表示要跳过整个子树。因为
    本身就是遍历的根节点,所以遍历会就此结束。
    当然,TreeWalker 真正的威力是可以在 DOM 结构中四处游走。如果不使用过滤器,单纯使用TreeWalker 的漫游能力同样可以在 DOM 树中访问
  • 元素,比如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let 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();
    }
    TreeWalker 类型也有一个名为 currentNode 的属性,表示遍历过程中上一次返回的节点(无论使用的是哪个遍历方法)。可以通过修改这个属性来影响接下来遍历的起点,如下面的例子所示:
    1
    2
    3
    let node = walker.nextNode(); 
    console.log(node === walker.currentNode); // true
    walker.currentNode = document.body; // 修改起点
    3、范围: 为了支持对页面更细致的控制,DOM2 Traversal and Range 模块定义了范围接口。范围可用于在文档中选择内容,而不用考虑节点之间的界限。(选择在后台发生,用户是看不到的。)范围在常规 DOM操作的粒度不够时可以发挥作用。
  • DOM范围: DOM2 在 Document 类型上定义了一个 createRange()方法,暴露在 document 对象上。使用这个方法可以创建一个 DOM 范围对象,如下所示:
    1
    let range = document.createRange();
    与节点类似,这个新创建的范围对象是与创建它的文档关联的,不能在其他文档中使用。然后可以使用这个范围在后台选择文档特定的部分。创建范围并指定它的位置之后,可以对范围的内容执行一些操作,从而实现对底层 DOM 树更精细的控制。
    每个范围都是 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
    <!DOCTYPE html> 
    <html>
    <body>
    <p id="p1"><b>Hello</b> world!</p>
    </body>
    </html>
    1
    2
    3
    4
    5
    let range1 = document.createRange(), 
    range2 = document.createRange(),
    p1 = document.getElementById("p1");
    range1.selectNode(p1);
    range2.selectNodeContents(p1);
    例子中的这两个范围包含文档的不同部分。range1 包含

    元素及其所有后代,而 range2 包含元素、文本节点”Hello”和文本节点” world!”.
    4、复杂选择: 要创建复杂的范围,需要使用 setStart()和 setEnd()方法。这两个方法都接收两个参数:参照节点和偏移量。对 setStart()来说,参照节点会成为 startContainer,而偏移量会赋值给 startOffset。对 setEnd()而言,参照节点会成为 endContainer,而偏移量会赋值给 endOffset。
    5、清理: 在使用完范围之后,最好调用 detach()方法把范围从创建它的文档中剥离。调用 detach()之后,就可以放心解除对范围的引用,以便垃圾回收程序释放它所占用的内存。下面是一个例子:

    1
    2
    range.detach(); // 从文档中剥离范围
    range = null; // 解除引用
    6、小结: DOM2 Style 模块定义了如何操作元素的样式信息。
     每个元素都有一个关联的 style 对象,可用于确定和修改元素特定的样式。
     要确定元素的计算样式,包括应用到元素身上的所有 CSS规则,可以使用getComputedStyle()方法。
     通过 document.styleSheets 集合可以访问文档上所有的样式表。DOM2 Traversal and Range 模块定义了与 DOM 结构交互的不同方式。
     NodeIterator 和 TreeWalker 可以对 DOM 树执行深度优先的遍历。
     NodeIterator 接口很简单,每次只能向前和向后移动一步。TreeWalker 除了支持同样的行为,还支持在 DOM 结构的所有方向移动,包括父节点、同胞节点和子节点。
     范围是选择 DOM 结构中特定部分并进行操作的一种方式。
     通过范围的选区可以在保持文档结构完好的同时从文档中移除内容,也可复制文档中相应的部分。
此页目录
2025-02-26 日报 Day109