技术干货程序猿阵线联盟-汇总各类技术干货让前端飞

dom总结:DOM2和DOM3中遍历和范围部分

2018-03-14  本文已影响0人  我不是大熊

遍历

要遍历文档的元素很简单,可以使用之前介绍的querySelectorAll()等方法,但这些方法都是元素Element层的,无法操作到Node层,比如文本节点或注释节点。“DOM2 级遍历和范围”模块定义了两个类型进行深度优先的遍历操作:NodeIterator 和 TreeWalker。(IE不支持)

NodeIterator

document.createNodeIterator()方法创建它的新实例,4个参数:

whatToShow 参数是一个位掩码,通过应用一或多个过滤器(filter)来确定要访问哪些节点。这个参数的值以常量形式在 NodeFilter 类型中定义(除了 NodeFilter.SHOW_ALL 之外,可以使用按位或操作符来组合多个选项):

可以通过 createNodeIterator()方法的 filter 参数来指定自定义的 NodeFilter 对象,或者 指定一个功能类似节点过滤器(node filter)的函数。每个 NodeFilter 对象只有一个方法,即 accept- Node();如果应该访问给定的节点,该方法返回 NodeFilter.FILTER_ACCEPT,如果不应该访问给 定的节点,该方法返回 NodeFilter.FILTER_SKIP。由于 NodeFilter 是一个抽象的类型,因此不能 直接创建它的实例。在必要时,只要创建一个包含 acceptNode()方法的对象,然后将这个对象传入 createNodeIterator()中即可。

    var root = document.querySelector("body");
    var filter = {
        acceptNode:function (node) {
            return node.tagName.toLowerCase() == "p"?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP;
        }
    };
    var iterator = document.createNodeIterator(root,NodeFilter.SHOW_ELEMENT,filter,false);
    var node = iterator.nextNode();
    while(node != null){
        console.log(node);
        node = iterator.nextNode();
    }

其中,NodeIterator 类型的两个主要方法是 nextNode()和 previousNode()。顾名思义,在深度优先 的 DOM 子树遍历中,nextNode()方法用于向前前进一步,而 previousNode()用于向后后退一步。 在刚刚创建的 NodeIterator 对象中,有一个内部指针指向根节点,因此第一次调用 nextNode()会返回根节点。当遍历到 DOM 子树的最后一个节点时,nextNode()返回 null。previousNode()方法的工作机制类似。

第三个参数也可以是一个与 acceptNode()方法类似的函数,常用的也是这种写法:

    var filter = function (node) {
        return node.tagName.toLowerCase() == "p" ?
                NodeFilter.FILTER_ACCEPT :
                NodeFilter.FILTER_SKIP;
    };

Firefox 3.5 之前的版本没有实现 createNodeIterator()方法,但却支持下一节要讨论的 createTreeWalker()方法。

TreeWalker

TreeWalker 是 NodeIterator 的一个更高级的版本。除了包括 nextNode()和 previousNode() 在内的相同的功能之外,这个类型还提供了下列用于在不同方向上遍历 DOM 结构的方法:

TreeWalker 类型还有一个属性,名叫 currentNode,表示任何遍历方法在上一次遍历中返回的节点。通过设置这个属性也可以修改遍历继续进行的起点。

filter 可以返回的值有所不同,除了 NodeFilter.FILTER_ACCEPT 和 NodeFilter. FILTER_SKIP 之外,还可以使用 NodeFilter.FILTER_REJECT。在使用 TreeWalker 对象时,NodeFilter.FILTER_SKIP 会跳过相应节点继续前进到子树中的下一个节点,而 NodeFilter.FILTER_REJECT 则会跳过相应节点及该节点的整个子树(跳过相应节点和该节点的整个子树之后继续前进遍历该节点后面的节点)。

而合理利用TreeWalker增加的方法,可以方便地遍历到想要的节点,以下不适用filter参数(设为null)也能筛选出li元素:

html:
<div class="div-cla">
    <p class="p-cla">我是段落</p>
    <ul class="ul-list">
        <li class="li-cla">第一个li</li>
        <li class="second-li">第二个li</li>
        <li class="third-li">第三个li</li>
        <li class="fourth-li">第四个li</li>
    </ul>
</div>
js:
var root = document.querySelector(".div-cla");
var walker = document.createTreeWalker(root,NodeFilter.SHOW_ELEMENT,null,false);
walker.nextNode();
walker.nextNode();//定位到了ul
var li = walker.firstChild();
while (li != null){
    //改变currentNode就跳过了第三个li
    if(li.classList.contains("second-li")){
        walker.currentNode = document.querySelector(".third-li");
     }
    console.log(li);
    li = walker.nextSibling();
 }

总结:对于NodeIterator和TreeWalker,由于 IE 中没有对 应的类型和方法,所以使用遍历的跨浏览器解决方案非常少见。

范围

用 DOM 范围实现简单选择

DOM2 级在 Document 类型中定义了 createRange()方法,创建范围并设置了其位置之后,还可以针对 范围的内容执行很多种操作,从而实现对底层 DOM 树的更精细的控制。
每个范围由一个 Range 类型的实例表示,下列属性提供了当前范 围在文档中的位置信息:

html:
<div class="p-con">
    <p>哈哈</p><p id="p1"><b>Hello</b>world!</p>
</div>
js:
    var range1 = document.createRange(),
        range2 = document.createRange(),
        p1 = document.getElementById("p1");
    range1.selectNode(p1);
    range2.selectNodeContents(p1);
    console.log(range1);
    console.log(range1.startContainer);//div.p-con
    console.log(range1.endContainer);//div.p-con
    //打印2:即范围内第一个节点在startContainer中的索引
    console.log(range1.startOffset);
    //打印3:即范围内最后一个节点在endContainer中的索引加1
    console.log(range1.endOffset);//3
    console.log(range1.commonAncestorContainer);//div.p-con

    console.log(range2);
    console.log(range2.startContainer);//p#p1
    console.log(range2.endContainer);//p#p1
    console.log(range2.startOffset);//0
    console.log(range2.endOffset);//2
    console.log(range2.commonAncestorContainer);//p#p1

为了更精细地控制将哪些节点包含在范围中,还可以使用下列方法:

用 DOM 范围实现复杂选择

要创建复杂的范围就得使用 setStart()和 setEnd()方法。这两个方法都接受两个参数:一个参 照节点和一个偏移量值。对 setStart()来说,参照节点会变成 startContainer,而偏移量值会变成 startOffset。对于 setEnd()来说,参照节点会变成 endContainer,而偏移量值会变成 endOffset。
这样可以实现和上面一样的效果:

    var range1 = document.createRange();
    var range2 = document.createRange();
    var p1 = document.getElementById("p1"), p1Index = -1;
    var i, len;
    for (i = 0, len = p1.parentNode.childNodes.length; i < len; i++) {
        if (p1.parentNode.childNodes[i] == p1) {
            p1Index = i;
            break;
        }
    }
    range1.setStart(p1.parentNode, p1Index);
    range1.setEnd(p1.parentNode, p1Index + 1);
    range2.setStart(p1, 0);
    range2.setEnd(p1, p1.childNodes.length);
    console.log(range1);
    console.log(range2);

显然使用setStart()和setEnd更直观更方便,以下进行精确控制找到llo wo:

var p1 = document.getElementById("p1");
var helloNode = p1.firstChild.firstChild;
var worldNode = p1.lastChild;
var range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
console.log(range);
操作 DOM 范围中的内容

在创建范围时 ,内部会为这个范围创建一个文档片段,范围所属的全部节点都被添加到了这个文档片段中。
创建了范围之后,就可以使用各种方法对范围的内容进行操作了(注意,表示范围的内部文档片段中的所有节点,都只是指向文档中相应节点的指针)。

接上:
range.deleteContents();
接上:
var fragment = range.extractContents();
console.log(fragment);
p1.parentNode.appendChild(fragment);
接上:
var copyFragment = range.cloneContents();
p1.parentNode.appendChild(copyFragment);
插入 DOM 范围中的内容
var span = document.createElement("span");
span.style.color = "red";
span.appendChild(document.createTextNode("Inserted text"));
range.insertNode(span);
接上:
//这句代码很重要
range.setEnd(helloNode,5);

var span = document.createElement("span");
span.style.backgroundColor = "red";
range.surroundContents(span);

效果图:


Snip20180313_13.png

所以环绕插入常用来突显某些文字.
注意点:代码中标注的这一句很重要,因为在环绕范围插入内容的时候,范围不能部分选中非文本节点并且只有一个边界(即是说选了部分该非文本节点和其他节点),否则会抛出错误.而标注的那一句把范围的开始和结束都限定在了<b></b>,所以没有问题.

折叠 DOM 范围

折叠范围:就是指范围中未选择文档的任何部分,在折叠范围时,其位置会落在文档中的两个部分之间,可能是范围选区的开始位置,也可能是结束位置。collapse()方法:一个参数,传true代表折叠到起点,false代表折叠到终点。

接上:
console.log(range.collapsed);//打印false
range.collapse(false);
console.log(range.collapsed);//打印true

检测某个范围是否处于折叠状态,可以帮我们确定范围中的两个节点是否紧密相邻:

html:
<p id="p2">Paragraph 2</p><p id="p3">Paragraph 3</p>
js:
var p2 = document.querySelector("#p2"),p3 = document.querySelector("#p3");
var range2 = document.createRange();
range2.setStartAfter(p2);
range2.setEndBefore(p3);
//打印false,因为p2后面p3前面什么都没有
console.log(range2.collapsed);
比较 DOM 范围

在有多个范围的情况下,可以使用 compareBoundaryPoints()方法来确定这些范围是否有公共的边界(起点或终点)。这个方法接受两个参数:表示比较方式的常量值和比较范围。表示比较方式的常量值如下所示:

复制 DOM 范围

可以使用 cloneRange()方法复制范围。这个方法会创建调用它的范围的一个副本。

var newRange = range.cloneRange();

新创建的范围与原来的范围包含相同的属性,而修改它的端点不会影响原来的范围。

清理 DOM 范围

在使用完范围之后,最好是调用 detach()方法,以便从创建范围的文档中分离出该范围。调用 detach()之后,就可以放心地解除对范围的引用,从而让垃圾回收机制回收其内存了。

range.detach();//分离范围
range = null;//垃圾回收

IE8 及更早版本中的范围

下次再继续补充。

上一篇下一篇

猜你喜欢

热点阅读