JavaScript-DOM(一)

举报
Johan Tian 发表于 2022/07/13 13:44:33 2022/07/13
【摘要】 DOM 一、概述 1、DOMDOM 是 JavaScript 操作网页的接口,全称为“文档对象模型”(Document Object Model)。它的作用是将网页转为一个 JavaScript 对象,从而可以用脚本进行各种操作(比如增删内容)。浏览器会根据 DOM 模型,将结构化文档(比如 HTML 和 XML)解析成一系列的节点,再由这些节点组成一个树状结构(DOM Tree)。所有的...

DOM

一、概述

1、DOM

DOM 是 JavaScript 操作网页的接口,全称为“文档对象模型”(Document Object Model)。它的作用将网页转为一个 JavaScript 对象,从而可以用脚本进行各种操作(比如增删内容)。

浏览器会根据 DOM 模型,将结构化文档(比如 HTML 和 XML)解析成一系列的节点,再由这些节点组成一个树状结构(DOM Tree)。所有的节点和最终的树状结构,都有规范的对外接口。

DOM 只是一个接口规范,可以用各种语言实现。所以严格地说,DOM 不是 JavaScript 语法的一部分,但是 DOM 操作是 JavaScript 最常见的任务,离开了 DOM,JavaScript 就无法控制网页。另一方面,JavaScript 也是最常用于 DOM 操作的语言。后面介绍的就是 JavaScript 对 DOM 标准的实现和用法。

2、节点

DOM 的最小组成单位叫做节点(node)。文档的树形结构(DOM 树),就是由各种不同类型的节点组成。每个节点可以看作是文档树的一片叶子。

节点的类型有七种。

  • Document:整个文档树的顶层节点
  • DocumentTypedoctype标签(比如<!DOCTYPE html>
  • Element:网页的各种HTML标签(比如<body><a>等)
  • Attribute:网页元素的属性(比如class="right"
  • Text:标签之间或标签包含的文本
  • Comment:注释
  • DocumentFragment:文档的片段

浏览器提供一个原生的节点对象Node,上面这七种节点都继承了Node,因此具有一些共同的属性和方法。

3、节点树

一个文档的所有节点,按照所在的层级,可以抽象成一种树状结构。这种树状结构就是 DOM 树。它有一个顶层节点,下一层都是顶层节点的子节点,然后子节点又有自己的子节点,就这样层层衍生出一个金字塔结构,倒过来就像一棵树。

浏览器原生提供document节点,代表整个文档。

document
// 整个文档树

文档的第一层有两个节点,第一个是文档类型节点(<!doctype html>),第二个是 HTML 网页的顶层容器标签<html>。后者构成了树结构的根节点(root node),其他 HTML 标签节点都是它的下级节点。

除了根节点,其他节点都有三种层级关系。

  • 父节点关系(parentNode):直接的那个上级节点
  • 子节点关系(childNodes):直接的下级节点
  • 同级节点关系(sibling):拥有同一个父节点的节点

DOM 提供操作接口,用来获取这三种关系的节点。比如,子节点接口包括firstChild(第一个子节点)和lastChild(最后一个子节点)等属性,同级节点接口包括nextSibling(紧邻在后的那个同级节点)和previousSibling(紧邻在前的那个同级节点)属性。

二、Node接口

所有 DOM 节点对象都继承了 Node 接口,拥有一些共同的属性和方法。这是 DOM 操作的基础。

1、属性

1.1 Node.prototype.nodeType节点类型

nodeType属性返回一个整数值,表示节点的类型

document.nodeType // 9

上面代码中,文档节点的类型值为9。

Node 对象定义了几个常量,对应这些类型值。

document.nodeType === Node.DOCUMENT_NODE // true

上面代码中,文档节点的nodeType属性等于常量Node.DOCUMENT_NODE

不同节点的nodeType属性值和对应的常量如下。

  • 文档节点(document):9,对应常量Node.DOCUMENT_NODE
  • 元素节点(element):1,对应常量Node.ELEMENT_NODE
  • 属性节点(attr):2,对应常量Node.ATTRIBUTE_NODE
  • 文本节点(text):3,对应常量Node.TEXT_NODE
  • 文档片断节点(DocumentFragment):11,对应常量Node.DOCUMENT_FRAGMENT_NODE
  • 文档类型节点(DocumentType):10,对应常量Node.DOCUMENT_TYPE_NODE
  • 注释节点(Comment):8,对应常量Node.COMMENT_NODE

确定节点类型时,使用nodeType属性是常用方法。

var node = document.documentElement.firstChild; // 获取到<head>节点
if (node.nodeType === Node.ELEMENT_NODE) {
  console.log('该节点是元素节点');
}

1.2 Node.prototype.nodeName节点名称

nodeName属性返回节点的名称

// HTML 代码如下
// <div id="d1">hello world</div>
var div = document.getElementById('d1');
div.nodeName // "DIV"
document.nodeName // #document

上面代码中,元素节点<div>nodeName属性就是大写的标签名DIV

不同节点的nodeName属性值如下。

  • 文档节点(document):#document
  • 元素节点(element):大写的标签名,如DIV
  • 属性节点(attr):属性的名称
  • 文本节点(text):#text
  • 文档片断节点(DocumentFragment):#document-fragment
  • 文档类型节点(DocumentType):文档的类型
  • 注释节点(Comment):#comment

1.3 Node.prototype.nodeValue当前节点文本

nodeValue属性返回一个字符串,表示当前节点本身的文本值,该属性可读写

只有文本节点(text)、注释节点(comment)和属性节点(attr)有文本值,因此这三类节点的nodeValue可以返回结果,其他类型的节点一律返回null。同样的,也只有这三类节点可以设置nodeValue属性的值,其他类型的节点设置无效。

// HTML 代码如下
// <div id="d1">hello world</div>
var div = document.getElementById('d1');
div.nodeValue // null
div.firstChild.nodeValue // "hello world"
var textNode = div.firstChild;
console.log(textNode) // 文本节点对象"hello world"
console.log(typeof textNode) // object
console.log(textNode.nodeType) // 3
console.log(textNode.nodeName) // #text
console.log(textNode.nodeValue) // 'hello world'
textNode.nodeValue = '123' // 修改文本
console.log(textNode.nodeValue) // '123'

上面代码中,div是元素节点,nodeValue属性返回nulldiv.firstChild是文本节点,所以可以返回文本值。

1.4 Node.prototype.textContent所有文本内容

textContent属性返回当前节点和它的所有后代节点的文本内容

// HTML 代码为
// <div id="divA">This is <span>some</span> text</div>
document.getElementById('divA').textContent
// This is some text

textContent属性自动忽略当前节点内部的 HTML 标签,返回所有文本内容。

该属性是可读写的,设置该属性的值,会用一个新的文本节点,替换所有原来的子节点。它还有一个好处,就是自动对 HTML 标签转义。这很适合用于用户提供的内容。

document.getElementById('foo').textContent = '<p>GoodBye!</p>'; // 写入文本

上面代码在插入文本时,会将<p>标签解释为文本,而不会当作标签处理。

对于文本节点(text)、注释节点(comment)和属性节点(attr),textContent属性的值与nodeValue属性相同。对于其他类型的节点,该属性会将每个子节点(不包括注释节点)的内容连接在一起返回。如果一个节点没有子节点,则返回空字符串。

文档节点(document)和文档类型节点(doctype)的textContent属性为null。如果要读取整个文档的内容,可以使用document.documentElement.textContent

1.5 Node.prototype.baseURI 获取网页路径

baseURI属性返回一个字符串,表示当前网页的绝对路径。浏览器根据这个属性,计算网页上的相对路径的 URL。该属性为只读

// 当前网页的网址为
// http://www.example.com/index.html
document.baseURI
// "http://www.example.com/index.html"

如果无法读到网页的 URL,baseURI属性返回null

该属性的值一般由当前网址的 URL(即window.location属性)决定,但是可以使用 HTML 的<base>标签,改变该属性的值。

<head>
<base href="http://www.example.com/page.html">
</head>
<script>
    document.baseURI // http://www.example.com/page.html   返回base标签的href值
</script>

设置了以后,baseURI属性就返回<base>标签设置的值。

1.6 Node.prototype.ownerDocument顶层文档对象

Node.ownerDocument属性返回当前节点所在的顶层文档对象,即document对象。

var d = p.ownerDocument;
d === document // true

document对象本身的ownerDocument属性,返回null

1.7 Node.prototype.nextSibling下一个同级节点

Node.nextSibling属性返回紧跟在当前节点后面的第一个同级节点。如果当前节点后面没有同级节点,则返回null

// HTML 代码如下
// <div id="d1">hello</div><div id="d2">world</div>
var d1 = document.getElementById('d1');
var d2 = document.getElementById('d2');
d1.nextSibling === d2 // true  // 如果d1和d2之间有空格,会返回文本节点,内容为空格

上面代码中,d1.nextSibling就是紧跟在d1后面的同级节点d2

注意,该属性还包括文本节点和注释节点(<!-- comment -->)。因此如果当前节点后面有空格,该属性会返回一个文本节点,内容为空格。

nextSibling属性可以用来遍历所有子节点。

var el = document.getElementById('div1').firstChild;
while (el !== null) {
  console.log(el.nodeName);
  el = el.nextSibling;
}

上面代码遍历div1节点的所有子节点。

1.8 Node.prototype.previousSibling上一个同级节点

previousSibling属性返回当前节点前面的、距离最近的一个同级节点。如果当前节点前面没有同级节点,则返回null

// HTML 代码如下
// <div id="d1">hello</div><div id="d2">world</div>
var d1 = document.getElementById('d1');
var d2 = document.getElementById('d2');
d2.previousSibling === d1 // true

上面代码中,d2.previousSibling就是d2前面的同级节点d1

注意,该属性还包括文本节点和注释节点。因此如果当前节点前面有空格,该属性会返回一个文本节点,内容为空格。

1.9 Node.prototype.parentNode父节点

parentNode属性返回当前节点的父节点。对于一个节点来说,它的父节点只可能是三种类型:元素节点(element)、文档节点(document)和文档片段节点(documentfragment)。

if (node.parentNode) {
  node.parentNode.removeChild(node); // node的父节点,然后删除子节点node
}

上面代码中,通过node.parentNode属性将node节点从文档里面移除。

文档节点(document)和文档片段节点(documentfragment)的父节点都是null。另外,对于那些生成后还没插入 DOM 树的节点,父节点也是null

1.10 Node.prototype.parentElement 父元素节点

parentElement属性返回当前节点的父元素节点。如果当前节点没有父节点,或者父节点类型不是元素节点,则返回null

if (node.parentElement) {
  node.parentElement.style.color = 'red';
}

上面代码中,父元素节点的样式设定了红色。

由于父节点只可能是三种类型:元素节点、文档节点(document)和文档片段节点(documentfragment)。parentElement属性相当于把后两种父节点都排除了。

1.11 Node.prototype.firstChild 第一个子节点,Node.prototype.lastChild 最后一个子节点

firstChild属性返回当前节点的第一个子节点,如果当前节点没有子节点,则返回null

// HTML 代码如下
// <p id="p1"><span>First span</span></p>
var p1 = document.getElementById('p1');
p1.firstChild.nodeName // "SPAN"

上面代码中,p元素的第一个子节点是span元素。

注意firstChild返回的除了元素节点,还可能是文本节点或注释节点

// HTML 代码如下
// <p id="p1">
//   <span>First span</span>
//  </p>
var p1 = document.getElementById('p1');
p1.firstChild.nodeName // "#text"

上面代码中,p元素与span元素之间有空白字符,这导致firstChild返回的是文本节点。

lastChild属性返回当前节点的最后一个子节点,如果当前节点没有子节点,则返回null。用法与firstChild属性相同。

1.12 Node.prototype.childNodes 所有子节点,类数组对象,动态集合

childNodes属性返回一个类似数组的对象NodeList集合),成员包括当前节点的所有子节点

var children = document.querySelector('ul').childNodes; // 包含元素节点、文本节点、注释节点

上面代码中,children就是ul元素的所有子节点。

使用该属性,可以遍历某个节点的所有子节点。

var div = document.getElementById('div1');
var children = div.childNodes;
for (var i = 0; i < children.length; i++) {
  // ...
}

文档节点(document)就有两个子节点:文档类型节点(docType)和 HTML 根元素节点。

var children = document.childNodes;
for (var i = 0; i < children.length; i++) {
  console.log(children[i].nodeType);
}
// 10
// 1

上面代码中,文档节点的第一个子节点的类型是10(即文档类型节点),第二个子节点的类型是1(即元素节点)。

注意,除了元素节点,childNodes属性的返回值还包括文本节点和注释节点。如果当前节点不包括任何子节点,则返回一个空的NodeList集合。由于NodeList对象是一个动态集合,一旦子节点发生变化,立刻会反映在返回结果之中。

1.13 Node.prototype.isConnected 是否在文档中,布尔值

isConnected属性返回一个布尔值,表示当前节点是否在文档之中

var test = document.createElement('p');
test.isConnected // false
document.body.appendChild(test);
test.isConnected // true

上面代码中,test节点是脚本生成的节点,没有插入文档之前,isConnected属性返回false,插入之后返回true

2、方法

2.1 Node.prototype.appendChild() 插入子节点到最后

appendChild()方法接受一个节点对象作为参数,将其作为最后一个子节点,插入当前节点。该方法的返回值就是插入文档的子节点

var p = document.createElement('p');
document.body.appendChild(p); // 有返回值,为已插入的p节点

上面代码新建一个<p>节点,将其插入document.body的尾部。

如果参数节点是 DOM 已经存在的节点,appendChild()方法会将其从原来的位置,移动到新位置。

var div = document.getElementById('myDiv');// myDiv是已存在的元素
document.body.appendChild(div); // 插入已存在元素,相当于移动位置

上面代码中,插入的是一个已经存在的节点myDiv,结果就是该节点会从原来的位置,移动到document.body的尾部。

如果appendChild()方法的参数是DocumentFragment节点,那么插入的是DocumentFragment的所有子节点,而不是DocumentFragment节点本身。返回值是一个空的DocumentFragment节点。

2.2 Node.prototype.hasChildNodes() 是否有子节点,布尔值

hasChildNodes方法返回一个布尔值表示当前节点是否有子节点

var foo = document.getElementById('foo');
if (foo.hasChildNodes()) {
  foo.removeChild(foo.childNodes[0]);
}

上面代码表示,如果foo节点有子节点,就移除第一个子节点。

注意子节点包括所有类型的节点,并不仅仅是元素节点。哪怕节点只包含一个空格,hasChildNodes方法也会返回true

判断一个节点有没有子节点,有许多种方法,下面是其中的三种。

  • node.hasChildNodes()
  • node.firstChild !== null
  • node.childNodes && node.childNodes.length > 0

hasChildNodes方法结合firstChild属性和nextSibling属性,可以遍历当前节点的所有后代节点

function DOMComb(parent, callback) {
  if (parent.hasChildNodes()) {
    for (var node = parent.firstChild; node; node = node.nextSibling) {
      DOMComb(node, callback);
    }
  }
  callback(parent);
}
// 用法
DOMComb(document.body, console.log)

上面代码中,DOMComb函数的第一个参数是某个指定的节点,第二个参数是回调函数。这个回调函数会依次作用于指定节点,以及指定节点的所有后代节点。

2.3 Node.prototype.cloneNode(Boolean) 克隆节点

cloneNode方法用于克隆一个节点它接受一个布尔值作为参数,表示是否同时克隆子节点。它的返回值是一个克隆出来的新节点。

var cloneUL = document.querySelector('ul').cloneNode(true);

该方法有一些使用注意点

(1)克隆一个节点,会拷贝该节点的所有属性,但是会丧失addEventListener方法和on-属性(即node.onclick = fn),添加在这个节点上的事件回调函数。

(2)该方法返回的节点不在文档之中,即没有任何父节点,必须使用诸如Node.appendChild这样的方法添加到文档之中。

(3)克隆一个节点之后,DOM 有可能出现两个有相同id属性(即id="xxx")的网页元素,这时应该修改其中一个元素的id属性。如果原节点有name属性,可能也需要修改。

2.4 Node.prototype.insertBefore() 插入节点到指定位置

insertBefore方法用于将某个节点插入父节点内部的指定位置

var insertedNode = parentNode.insertBefore(newNode, referenceNode);
// newNode新节点将插入到parentNode内部的referenceNode节点前面,并返回新节点

insertBefore方法接受两个参数,第一个参数是所要插入的节点newNode,第二个参数是父节点parentNode内部的一个子节点referenceNodenewNode将插在referenceNode这个子节点的前面。返回值是插入的新节点newNode

var p = document.createElement('p');
document.body.insertBefore(p, document.body.firstChild);// 把p节点插入到body内部第一个元素的前面

上面代码中,新建一个<p>节点,插在document.body.firstChild的前面,也就是成为document.body的第一个子节点。

如果insertBefore方法的第二个参数为null,则新节点将插在当前节点内部的最后位置,即变成最后一个子节点。

var p = document.createElement('p');
document.body.insertBefore(p, null);

上面代码中,p将成为document.body的最后一个子节点。这也说明insertBefore的第二个参数不能省略。

注意,如果所要插入的节点是当前 DOM 现有的节点,则该节点将从原有的位置移除,插入新的位置。

由于不存在insertAfter方法,如果新节点要插在父节点的某个子节点后面,可以用insertBefore方法结合nextSibling属性模拟。

插入到某个子节点后面

parent.insertBefore(s1, s2.nextSibling);

上面代码中,parent是父节点,s1是一个全新的节点,s2是可以将s1节点,插在s2节点的后面。如果s2是当前节点的最后一个子节点,则s2.nextSibling返回null,这时s1节点会插在当前节点的最后,变成当前节点的最后一个子节点,等于紧跟在s2的后面。

如果要插入的节点是DocumentFragment类型,那么插入的将是DocumentFragment的所有子节点,而不是DocumentFragment节点本身。返回值将是一个空的DocumentFragment节点。

2.5 Node.prototype.removeChild() 删除子节点,并返回

removeChild方法接受一个子节点作为参数,用于从当前节点移除该子节点。返回值是移除的子节点

var divA = document.getElementById('A');
divA.parentNode.removeChild(divA); // 先获取父节点再调用删除子节点方法,并指定删除的子节点为divA

上面代码移除了divA节点。注意,这个方法是在divA的父节点上调用的,不是在divA上调用的。

下面是如何移除当前节点的所有子节点

var element = document.getElementById('top');
while (element.firstChild) {
  element.removeChild(element.firstChild);
}

被移除的节点依然存在于内存之中,但不再是 DOM 的一部分。所以,一个节点移除以后,依然可以使用它,比如插入到另一个节点下面。

如果参数节点不是当前节点的子节点,removeChild方法将报错。

2.6 Node.prototype.replaceChild() 替换子节点,返回被替换的

replaceChild方法用于将一个新的节点,替换当前节点的某一个子节点返回被替换的子节点。

var replacedNode = parentNode.replaceChild(newChild, oldChild);

上面代码中,replaceChild方法接受两个参数,第一个参数newChild是用来替换的新节点,第二个参数oldChild是将要替换走的子节点。返回值是替换走的那个节点oldChild

var divA = document.getElementById('divA');
var newSpan = document.createElement('span');
newSpan.textContent = 'Hello World!';
divA.parentNode.replaceChild(newSpan, divA);

上面代码是将指定节点divA替换为新节点newSpan

2.7 Node.prototype.contains() 参数节点是否为后代节点或当前节点,返回布尔值

contains方法返回一个布尔值,表示参数节点是否满足以下三个条件之一

  • 参数节点为当前节点。
  • 参数节点为当前节点的子节点。
  • 参数节点为当前节点的后代节点。

当前节点中是否包含参数节点

document.body.contains(node)

上面代码检查参数节点node,是否包含在当前文档之中。

注意,当前节点传入contains方法,返回true

nodeA.contains(nodeA) // true

2.8 Node.prototype.compareDocumentPosition() 和contains方法类似,返回值可算出位置关系

compareDocumentPosition方法的用法,contains方法完全一致,返回一个六个比特位的二进制值,表示参数节点与当前节点的关系

二进制值 十进制值 含义
000000 0 两个节点相同
000001 1 两个节点不在同一个文档(即有一个节点不在当前文档)
000010 2 参数节点在当前节点的前面
000100 4 参数节点在当前节点的后面
001000 8 参数节点包含当前节点
010000 16 当前节点包含参数节点
100000 32 浏览器内部使用
// HTML 代码如下
// <div id="mydiv">
//   <form><input id="test" /></form>
// </div>
var div = document.getElementById('mydiv');
var input = document.getElementById('test');
div.compareDocumentPosition(input) // 20 当前节点包含参数节点(16)+ 参数节点在当前节点的后面(4)
input.compareDocumentPosition(div) // 10 参数节点包含当前节点(8)+ 参数节点在当前节点的前面(2)

上面代码中,节点div包含节点input(二进制010000),而且节点input在节点div的后面(二进制000100),所以第一个compareDocumentPosition方法返回20(二进制010100,即010000 + 000100),第二个compareDocumentPosition方法返回10(二进制001010)。

由于compareDocumentPosition返回值的含义,定义在每一个比特位上,所以如果要检查某一种特定的含义,就需要使用比特位运算符。

var head = document.head;
var body = document.body;
if (head.compareDocumentPosition(body) & 4) { // 4表示 参数节点在当前节点的后面
  console.log('文档结构正确');
} else {
  console.log('<body> 不能在 <head> 前面');
}

上面代码中,compareDocumentPosition的返回值与4(又称掩码)进行与运算(&),得到一个布尔值,表示是否在前面。

2.9 Node.prototype.isEqualNode() 节点是否相等 Node.prototype.isSameNode() 节点是否相同

isEqualNode方法返回一个布尔值,用于检查两个节点是否相等。所谓相等的节点,指的是两个节点的类型相同、属性相同、子节点相同。

var p1 = document.createElement('p');
var p2 = document.createElement('p');
p1.isEqualNode(p2) // true

isSameNode方法返回一个布尔值,表示两个节点是否为同一个节点

var p1 = document.createElement('p');
var p2 = document.createElement('p');
p1.isSameNode(p2) // false
p1.isSameNode(p1) // true

2.10 Node.prototype.normalize() 将当前节点和它的后代节点”规范化“

normalize方法 将当前节点和它的后代节点”规范化“ 。在一个"规范化"后的DOM树中,不存在一个空的文本节点,或者两个相邻的文本节点。

注1:“空的文本节点”并不包括空白字符(空格,换行等)构成的文本节点。

注2:两个以上相邻文本节点的产生原因包括:

  1. 通过脚本调用有关的DOM接口进行了文本节点的插入和分割等。
  2. HTML中超长的文本节点会被浏览器自动分割为多个相邻文本节点。
var wrapper = document.createElement("div");
wrapper.appendChild(document.createTextNode("Part 1 "));
wrapper.appendChild(document.createTextNode("Part 2 "));
// 这时(规范化之前),wrapper.childNodes.length === 2
// wrapper.childNodes[0].textContent === "Part 1 "
// wrapper.childNodes[1].textContent === "Part 2 "
wrapper.normalize();
// 现在(规范化之后), wrapper.childNodes.length === 1
// wrapper.childNodes[0].textContent === "Part 1 Part 2"

上面代码使用normalize方法之前,wrapper节点有两个毗邻的文本子节点。使用normalize方法之后,两个文本子节点被合并成一个。

该方法是Text.splitText的逆方法,可以查看《Text 节点对象》一章,了解更多内容。

2.11 Node.prototype.getRootNode() 获取根节点(document)

getRootNode()方法返回当前节点所在文档的根节点document,与ownerDocument属性的作用相同

document.body.firstChild.getRootNode() === document
// true
document.body.firstChild.getRootNode() === document.body.firstChild.ownerDocument
// true

该方法可用于document节点自身,这一点与document.ownerDocument不同。

document.getRootNode() // document
document.ownerDocument // null

三、NodeList 接口 与 HTMLCollection 接口 (节点的集合)

节点都是单个对象,有时需要一种数据结构,能够容纳多个节点。DOM 提供两种节点集合,用于容纳多个节点:NodeListHTMLCollection

这两种集合都属于接口规范。许多 DOM 属性和方法,返回的结果是NodeList实例或HTMLCollection实例。主要区别是,NodeList可以包含各种类型的节点,HTMLCollection只能包含 HTML 元素节点

1、NodeList 接口 (各类节点的集合)

1.1 概述

NodeList实例是一个类似数组的对象,它的成员是节点对象。通过以下方法可以得到NodeList实例。

  • Node.childNodes 属性
  • document.querySelectorAll()等节点搜索方法
document.body.childNodes instanceof NodeList // true
/*
<ul>
    <li></li>
    <li></li>
    <li></li>
</ul>*/
var $li = document.querySelectorAll('li');
console.log($li) // NodeList(3) [li, li, li]
console.log($li instanceof NodeList) // true

NodeList实例很像数组,可以使用length属性和forEach方法。但是,它不是数组,不能使用poppush之类数组特有的方法。

var children = document.body.childNodes;
Array.isArray(children) // false
children.length // 34
children.forEach(console.log)

上面代码中,NodeList 实例children不是数组,但是具有length属性和forEach方法。

如果NodeList实例要使用数组方法,可以将其转为真正的数组

var children = document.body.childNodes;
var nodeArr = Array.prototype.slice.call(children);

除了使用forEach方法遍历 NodeList 实例,还可以使用for循环

var children = document.body.childNodes;
for (var i = 0; i < children.length; i++) {
  var item = children[i];
}

注意,NodeList 实例可能是动态集合,也可能是静态集合。所谓动态集合就是一个活的集合,DOM 删除或新增一个相关节点,都会立刻反映在 NodeList 实例。目前,只有Node.childNodes返回的是一个动态集合,其他的 NodeList 都是静态集合。

var children = document.body.childNodes;
children.length // 18
document.body.appendChild(document.createElement('p'));
children.length // 19

上面代码中,文档增加一个子节点,NodeList 实例childrenlength属性就增加了1。

1.2 NodeList.prototype.length

length属性返回 NodeList 实例包含的节点数量。

document.querySelectorAll('xxx').length
// 0

上面代码中,document.querySelectorAll返回一个 NodeList 集合。对于那些不存在的 HTML 标签,length属性返回0

1.3 NodeList.prototype.forEach()

forEach方法用于遍历 NodeList 的所有成员。它接受一个回调函数作为参数,每一轮遍历就执行一次这个回调函数,用法与数组实例的forEach方法完全一致。

var children = document.body.childNodes;
children.forEach(function f(item, i, list) { // 参数:当前成员、当前索引、NodeList实例
  // ...
}, this); // forEach参数二: 绑定回调函数内部的this,可省略

上面代码中,回调函数f的三个参数依次是当前成员、位置和当前 NodeList 实例forEach方法的第二个参数,用于绑定回调函数内部的this,该参数可省略。

1.4 NodeList.prototype.item() 接受一个索引参数,返回该索引上的成员(节点)

item方法接受一个整数值作为参数,表示成员的位置,返回该位置上的成员。

document.body.childNodes.item(0)

上面代码中,item(0)返回第一个成员。

如果参数值大于实际长度,或者索引不合法(比如负数),item方法返回null。如果省略参数,item方法会报错。

所有类似数组的对象,都可以使用方括号运算符取出成员。一般情况下,都是使用方括号运算符,而不使用item方法。

document.body.childNodes[0]

1.5 NodeList.prototype.keys(),NodeList.prototype.values(),NodeList.prototype.entries() 遍历器对象,结合for…of遍历每一个成员的信息

这三个方法都返回一个 ES6 的遍历器对象,可以通过for...of循环遍历获取每一个成员的信息。区别在于,keys()返回键名的遍历器,values()返回键值的遍历器,entries()返回的遍历器同时包含键名和键值的数组

var children = document.body.childNodes;
for (var key of children.keys()) {
  console.log(key); // 返回键名
}
// 0
// 1
// 2
// ...
for (var value of children.values()) {
  console.log(value); // 返回键值 (即节点名)
}
// #text
// <script>
// ...
for (var entry of children.entries()) {
  console.log(entry); // 返回键名和键值的数组
}
// Array [ 0, #text ]
// Array [ 1, <script> ]
// ...

2、HTMLCollection 接口 (HTML元素节点的集合)

2.1 概述

HTMLCollection是一个节点对象的集合,只能包含元素节点(element),不能包含其他类型的节点。它的返回值是一个类似数组的对象,但是与NodeList接口不同,HTMLCollection没有forEach方法,只能使用for循环遍历。

返回HTMLCollection实例的,主要是一些Document对象的集合属性,比如document.linksdocument.formsdocument.images等。

document.links instanceof HTMLCollection // true

HTMLCollection实例都是动态集合,节点的变化会实时反映在集合中。

如果元素节点有idname属性,那么HTMLCollection实例上面,可以使用id属性或name属性引用该节点元素。如果没有对应的节点,则返回null

// HTML 代码如下
// <img id="pic" src="http://example.com/foo.jpg">
var pic = document.getElementById('pic');
document.images.pic === pic // true

上面代码中,document.images是一个HTMLCollection实例,可以通过<img>元素的id属性值,从HTMLCollection实例上取到这个元素。

各js选择器返回的数据对比
<ul>
    <li id="li1"></li>
    <li class="li2"></li>
    <li name='liName'></li>
 </ul>

<script>
    var li1 = document.getElementById('li1');
    var li2 = document.querySelector('#li1');
    var li3 = document.getElementsByClassName('li2');
    var li4 = document.getElementsByTagName('li');
    var li5 = document.getElementsByName('liName');
    var li6 = document.querySelectorAll('li');
    console.log(li1) // <li id="li1"></li>
    console.log(li2) // <li id="li1"></li>
    console.log(li3) // HTMLCollection [li.li2]
    console.log(li4) // HTMLCollection(3) [li#li1, li.li2, li, li1: li#li1, liName: li]
    console.log(li5) // NodeList [li]
    console.log(li6) // NodeList(3) [li#li1, li.li2, li]
</script>

2.2 HTMLCollection.prototype.length

length属性返回HTMLCollection实例包含的成员数量。

document.links.length // 18

2.3 HTMLCollection.prototype.item() 和方括号访问一样

item方法接受一个整数值作为参数,表示成员的位置,返回该位置上的成员。

var c = document.images;
var img0 = c.item(0);

上面代码中,item(0)表示返回0号位置的成员。由于方括号运算符也具有同样作用,而且使用更方便,所以一般情况下,总是使用方括号运算符

如果参数值超出成员数量或者不合法(比如小于0),那么item方法返回null

2.4 HTMLCollection.prototype.namedItem() 参数为id或name,返回元素节点

namedItem方法的参数是一个字符串,表示id属性或name属性的值,返回对应的元素节点。如果没有对应的节点,则返回null

// HTML 代码如下
// <img id="pic" src="http://example.com/foo.jpg">
var pic = document.getElementById('pic');
document.images.namedItem('pic') === pic // true

四、ParentNode 接口(父节点接口),ChildNode 接口(子节点接口)

节点对象除了继承 Node 接口以外,还会继承其他接口。ParentNode接口表示当前节点是一个父节点,提供一些处理子节点的方法ChildNode接口表示当前节点是一个子节点,提供一些相关方法。

1、ParentNode 接口

如果当前节点是父节点,就会继承ParentNode接口。由于只有元素节点(element)、文档节点(document)和文档片段节点(documentFragment)拥有子节点,因此只有这三类节点会继承ParentNode接口。

1.1 属性

(1)ParentNode.children (返回HTMLCollection实例,当前节点的所有元素子节点)

children属性返回一个HTMLCollection实例,成员是当前节点的所有元素子节点。该属性只读

下面是遍历某个节点的所有元素子节点的示例。

for (var i = 0; i < el.children.length; i++) {
  // ...
}

注意,children属性只包括元素子节点,不包括其他类型的子节点(比如文本子节点)。如果没有元素类型的子节点,返回值HTMLCollection实例的length属性为0

另外,HTMLCollection是动态集合,会实时反映 DOM 的任何变化。

(2)ParentNode.firstElementChild 第一个元素子节点

firstElementChild属性返回当前节点的第一个元素子节点。如果没有任何元素子节点,则返回null

document.firstElementChild.nodeName
// "HTML"

上面代码中,document节点的第一个元素子节点是``。

(3)ParentNode.lastElementChild 最后一个元素子节点

lastElementChild属性返回当前节点的最后一个元素子节点,如果不存在任何元素子节点,则返回null

document.lastElementChild.nodeName
// "HTML"

上面代码中,document节点的最后一个元素子节点是``(因为document只包含这一个元素子节点)。

(4)ParentNode.childElementCount 所有元素子节点的数量

childElementCount属性返回一个整数,表示当前节点的所有元素子节点的数目。如果不包含任何元素子节点,则返回0

document.body.childElementCount // 13

1.2 方法

ParentNode.append() 插入子节点于后,ParentNode.prepend() 插入子节点于前

append方法为当前节点追加一个或多个子节点,位置是最后一个元素子节点的后面

该方法不仅可以添加元素子节点,还可以添加文本子节点。

var parent = document.body;
// 添加元素子节点
var p = document.createElement('p');
parent.append(p);
// 添加文本子节点
parent.append('Hello');
// 添加多个元素子节点
var p1 = document.createElement('p');
var p2 = document.createElement('p');
parent.append(p1, p2);
// 添加元素子节点和文本子节点
var p = document.createElement('p');
parent.append('Hello', p);

注意,该方法没有返回值

prepend方法为当前节点追加一个或多个子节点,位置是第一个元素子节点的前面。它的用法与append方法完全一致,也是没有返回值

2、ChildNode 接口

如果一个节点有父节点,那么该节点就继承了ChildNode接口。

2.1 ChildNode.remove() 移除当前节点

remove方法用于从父节点移除当前节点

el.remove()

上面代码在 DOM 里面移除了el节点。

2.2 ChildNode.before() 插入同级节点于前,ChildNode.after() 插入同级节点于后

before方法用于在当前节点的前面,插入一个或多个同级节点。两者拥有相同的父节点。

注意,该方法不仅可以插入元素节点,还可以插入文本节点。

var p = document.createElement('p');
var p1 = document.createElement('p');
// 插入元素节点
el.before(p);
// 插入文本节点
el.before('Hello');
// 插入多个元素节点
el.before(p, p1);
// 插入元素节点和文本节点
el.before(p, 'Hello');

after方法用于在当前节点的后面,插入一个或多个同级节点,两者拥有相同的父节点。用法与before方法完全相同。

2.3 ChildNode.replaceWith() 用参数节点替换当前节点

replaceWith方法**使用参数节点,替换当前节点。**参数可以是元素节点,也可以是文本节点。

var span = document.createElement('span');
el.replaceWith(span);

上面代码中,el节点将被span节点替换。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。