浏览器DOM:你知道HTML的节点有哪几种吗?

DOM API介绍

文档类型模型用来描述文档,这里的文档,这里特指HTML文档(也包含XML,但本文不讨论XML)。同时它又是一个“对象模型”,这意味着它是以对象这样的概念描述HTML文档。
HTML文档,是由标签嵌套而成的树形结构,因此,DOM也是使用树形的对象模型来描述HTML文档。
DOM API大致分为4个部分:

  • 节点:DOM树形结构中的节点相关API。
  • 事件:触发和监听事件相关API。
  • Range:操作文字范围相关API。
  • 遍历:遍历DOM需要的API。
节点

DOM的树形结构所有节点有统一的接口Node。按照继承关系,介绍一下节点的类型。

除了Document和DocumentFragment以外,都有与之对应的HTTP写法:

Element: <tagname>...</tagname>
Text: text
Comment: <!-- comments -->
DocumentType: <!Doctype html>
ProcessingInstruction: <?a 1?>

我们需要关注的是:Document、Element、Text节点。

Node

Node是DOM树继承关系的跟节点,它定义了DOM节点在DOM树上的操作,首先Node提供了一组属性,来表示它在DOM树中的关系:

  • parentNode
  • childNodes
  • firstChild
  • lastChild
  • nextSibling
  • previousSibling

从命名上,可以清晰地看出这组数据提供了前、后、父、子关系,有了这几个属性,我们可以很方便的根据相对位置获取元素。Node中也提供了操作DOM树的API:

  • appendChild
  • insertBefore
  • removeChild
  • replaceChild

原生没有提供insertAfter方法,可以使用appendChild和insertBefore两个API实现。这修改类型的API,全都是在父元素上操作的,比如我们要删除一个元素的上一个元素,就先用parentNode获取其父元素。
另外,Node还提供了一些高级用法:

  • compareDocumentPosition 是以用于比较两个节点关系的函数。
  • contains 检查一个节点是否包含另一个节点的函数
  • isEqualNode 检查两个节点是否相同
  • isSamaNode 检查两个节点是否为同一个节点,实际上在JavaScript 中可以用"==="  
  • cloneNode 复制一个节点,如果传入参数true,则会连同子元素做深拷贝。

DOM标准规定节点必须从文档的create方法创建出来,不能使用原生的JavaScript的new运算。于是document对象有这些方法:

  • createElement
  • createTextNode
  • createCDATASection
  • createComment
  • createProcessingInstruction
  • createDocumentFragment
  • createDocumentType

Element 与 Attribute
Element表示元素,它是Node的元素。首先,我们可以把元素的Attribute当作字符串来看待,这样就有以下的API:

  • getAttribute
  • setAttribute
  • removeAttribute
  • hasAttribute

如果你追求极致的性能,还可以把Attribute当作节点:

  • createElement
  • createTextNode

查找元素document节点提供了查找元素的能力。比如有下面的几种。querySelector

  • querySelectorAll
  • getElementById
  • getElementsByName
  • getElementsByTagName
  • getElementsByClassName

我们需要注意,getElementById、getElementsByName、getElementsByTagName、getElementsByClassName,这几个API的性能高于querySelector。
而 getElementsByName、getElementsByTagName、getElementsByClassName 获取的集合并非数组,而是一个能够动态更新的集合。

var collection = document.getElementsByClassName('winter');
console.log(collection.length); // null
var winter = document.createElement('div');
winter.setAttribute('class', 'winter')
document.documentElement.appendChild(winter)
console.log(collection.length); // [Element]
遍历

前面已经提到过,通过Node的相关属性,我们可以用JavaScript遍历整个树。实际上,DOM API中还提供了NodeIterator 和 TreeWalker 来遍历树。
比起直接用属性遍历,NodeIterator 和 TreeWalker提供了过滤功能,还可以把属性节点也包含在遍历之内。
NodeIterator的基本用法示例如下:

var iterator = document.createNodeIterator(document.body, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMM
var node;
while(node = iterator.nextNode())
{
    console.log(node);
}

这个API设计非常老派,原因主要有两点,一是循环没有类似的“hasNext”这样的方法,而是直接以nextNode返回空来标志结束,二是第二个参数是掩码,这两个设计都是传统C语言里比较常见的用法。

var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, false)
var node;
while(node = walker.nextNode())
{
    if(node.tagName === "p")
        node.nextSibling();
    console.log(node);
}

比起NodeIterator,TreeWalker多了在DOM树上自由移动当前节点的能力,一般来说,这种API用于“跳过”某些节点,或者重复遍历某些节点。
总的来说,并不推荐使用这两个API,建议需要遍历DOM的时候,直接使用递归和Node的属性。

Range

Range API 表示一个HTML上的范围,这个范围是以文字为最小单位的,所以Range不一定包含完整的节点,它可能是Text节点中的一段,也可以是头尾两个Text的一部分加上中间的元素。
我们通过 Range API 可以比节点 API 更精确地操作 DOM 树,凡是 节点 API 能做到的,Range API都可以做到,而且可以做到更高性能,但是 Range API 使用起来比较麻烦,所以在实际项目中,并不常用,只有做底层框架和富文本编辑对它有强需求。
创建Range一般是通过设置它的起止来实现:

var range = new Range(),
    firstText = p.childNodes[1],
    secondText = em.firstChild
range.setStart(firstText, 9) // do not forget the leading space
range.setEnd(secondText, 4)

通过Range也可以从用户选中区域创建,这样的Range用于处理用户选中区域:

var range = document.getSelection().getRangeAt(0);

更改 Range 选中区段内容的方式主要是取出和插入,分别由extractContents和insertNode来实现。

var fragment = range.extractContents()
range.insertNode(document.createTextNode("aaaa"))

最后一个完整的例子:

var range = new Range(),
    firstText = p.childNodes[1],
    secondText = em.firstChild
range.setStart(firstText, 9) // do not forget the leading space
range.setEnd(secondText, 4)
var fragment = range.extractContents()
range.insertNode(document.createTextNode("aaaa"))
此文章为7月Day30学习笔记,内容来源于极客时间《重学前端》,日拱一卒,每天进步一点点💪💪

豪猪
4 声望4 粉丝

undefined