Node 接口
所有 DOM 节点对象都继承了 Node 接口,拥有一些共同的属性和方法。这是 DOM 操作的基础。
属性
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常数值 | 描述 |
---|---|---|
Node.ELEMENT_NODE | 1 | 元素节点 |
Node.ATTRIBUTE_NODE | 2 | 属性节点 |
Node.TEXT_NODE | 3 | 文本节点 |
Node.CDATA_SECTION_NODE | 4 | 字符数据节点(文本不会被解析器解析) |
Node.ENTITY_REFERENCE_NODE | 5 | 实体引用节点 |
Node.ENTITY_NODE | 6 | 实体节点 |
Node.PROCESSING_INSTRUCTION_NODE | 7 | 处理指令节点 |
Node.COMMENT_NODE | 8 | 注释节点 |
Node.DOCUMENT_NODE | 9 | 文档节点(DOM树的根节点) |
Node.DOCUMENT_TYPE_NODE | 10 | 向为文档定义的实体提供接口 |
Node.DOCUMENT_FRAGMENT_NODE | 11 | 表示邻接节点和它们的子树 |
Node.NOTATION_NODE | 12 | 代表一个符号在DTD中的声明 |
确定节点类型时,使用
nodeType
属性是常用方法。
var node = document.documentElement.firstChild; if (node.nodeType === Node.ELEMENT_NODE) { console.log('该节点是元素节点'); }
Node.prototype.nodeName
nodeName
属性返回节点的名称。
// HTML 代码如下 // <div id="d1">hello world</div> var div = document.getElementById('d1'); div.nodeName // "DIV"
上面代码中,元素节点
<div>
的
nodeName
属性就是大写的标签名
DIV
。
不同节点的
nodeName
属性值如下。
-
文档节点(document):
#document
- 元素节点(element):大写的标签名
- 属性节点(attr):属性的名称
-
文本节点(text):
#text
-
文档片断节点(DocumentFragment):
#document-fragment
- 文档类型节点(DocumentType):文档的类型
-
注释节点(Comment):
#comment
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"
上面代码中,
div
是元素节点,
nodeValue
属性返回
null
。
div.firstChild
是文本节点,所以可以返回文本值。
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
。
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>
标签,改变该属性的值。
<base href="http://www.example.com/page.html">
设置了以后,
baseURI
属性就返回
<base>
标签设置的值。
Node.prototype.ownerDocument
Node.ownerDocument
属性返回当前节点所在的顶层文档对象,即
document
对象。
var d = p.ownerDocument; d === document // true
document
对象本身的
ownerDocument
属性,返回
null
。
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.nextSibling
就是紧跟在
d1
后面的同级节点
d2
。
注意,该属性还包括文本节点和注释节点(
<!-- comment -->
)。因此如果当前节点后面有空格,该属性会返回一个文本节点,内容为空格。
nextSibling
属性可以用来遍历所有子节点。
var el = document.getElementById('div1').firstChild; while (el !== null) { console.log(el.nodeName); el = el.nextSibling; }
上面代码遍历
div1
节点的所有子节点。
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
。
注意,该属性还包括文本节点和注释节点。因此如果当前节点前面有空格,该属性会返回一个文本节点,内容为空格。
Node.prototype.parentNode
parentNode
属性返回当前节点的父节点。对于一个节点来说,它的父节点只可能是三种类型:元素节点(element)、文档节点(document)和文档片段节点(documentfragment)。
if (node.parentNode) { node.parentNode.removeChild(node); }
上面代码中,通过
node.parentNode
属性将
node
节点从文档里面移除。
文档节点(document)和文档片段节点(documentfragment)的父节点都是
null
。另外,对于那些生成后还没插入 DOM 树的节点,父节点也是
null
。
Node.prototype.parentElement
parentElement
属性返回当前节点的父元素节点。如果当前节点没有父节点,或者父节点类型不是元素节点,则返回
null
。
if (node.parentElement) { node.parentElement.style.color = 'red'; }
上面代码中,父元素节点的样式设定了红色。
由于父节点只可能是三种类型:元素节点、文档节点(document)和文档片段节点(documentfragment)。
parentElement
属性相当于把后两种父节点都排除了。
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
属性相同。
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
对象是一个动态集合,一旦子节点发生变化,立刻会反映在返回结果之中。
Node.prototype.isConnected
isConnected
属性返回一个布尔值,表示当前节点是否在文档之中。
var test = document.createElement('p'); test.isConnected // false document.body.appendChild(test); test.isConnected // true
上面代码中,
test
节点是脚本生成的节点,没有插入文档之前,
isConnected
属性返回
false
,插入之后返回
true
。
方法
Node.prototype.appendChild()
appendChild()
方法接受一个节点对象作为参数,将其作为最后一个子节点,插入当前节点。该方法的返回值就是插入文档的子节点。
var p = document.createElement('p'); document.body.appendChild(p);
上面代码新建一个
<p>
节点,将其插入
document.body
的尾部。
如果参数节点是 DOM 已经存在的节点,
appendChild()
方法会将其从原来的位置,移动到新位置。
var div = document.getElementById('myDiv'); document.body.appendChild(div);
上面代码中,插入的是一个已经存在的节点
myDiv
,结果就是该节点会从原来的位置,移动到
document.body
的尾部。
如果
appendChild()
方法的参数是
DocumentFragment
节点,那么插入的是
DocumentFragment
的所有子节点,而不是
DocumentFragment
节点本身。返回值是一个空的
DocumentFragment
节点。
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
函数的第一个参数是某个指定的节点,第二个参数是回调函数。这个回调函数会依次作用于指定节点,以及指定节点的所有后代节点。
Node.prototype.cloneNode()
cloneNode
方法用于克隆一个节点。它接受一个布尔值作为参数,表示是否同时克隆子节点。它的返回值是一个克隆出来的新节点。
var cloneUL = document.querySelector('ul').cloneNode(true);
该方法有一些使用注意点。
(1)克隆一个节点,会拷贝该节点的所有属性,但是会丧失
addEventListener
方法和
on-
属性(即
node.onclick = fn
),添加在这个节点上的事件回调函数。
(2)该方法返回的节点不在文档之中,即没有任何父节点,必须使用诸如
Node.appendChild
这样的方法添加到文档之中。
(3)克隆一个节点之后,DOM 有可能出现两个有相同
id
属性(即
id="xxx"
)的网页元素,这时应该修改其中一个元素的
id
属性。如果原节点有
name
属性,可能也需要修改。
Node.prototype.insertBefore()
insertBefore
方法用于将某个节点插入父节点内部的指定位置。
var insertedNode = parentNode.insertBefore(newNode, referenceNode);
insertBefore
方法接受两个参数,第一个参数是所要插入的节点
newNode
,第二个参数是父节点
parentNode
内部的一个子节点
referenceNode
。
newNode
将插在
referenceNode
这个子节点的前面。返回值是插入的新节点
newNode
。
var p = document.createElement('p'); document.body.insertBefore(p, document.body.firstChild);
上面代码中,新建一个
<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
节点。
Node.prototype.removeChild()
removeChild
方法接受一个子节点作为参数,用于从当前节点移除该子节点。返回值是移除的子节点。
var divA = document.getElementById('A'); divA.parentNode.removeChild(divA);
上面代码移除了
divA
节点。注意,这个方法是在
divA
的父节点上调用的,不是在
divA
上调用的。
下面是如何移除当前节点的所有子节点。
var element = document.getElementById('top'); while (element.firstChild) { element.removeChild(element.firstChild); }
被移除的节点依然存在于内存之中,但不再是 DOM 的一部分。所以,一个节点移除以后,依然可以使用它,比如插入到另一个节点下面。
如果参数节点不是当前节点的子节点,
removeChild
方法将报错。
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
替换走。
Node.prototype.contains()
contains
方法返回一个布尔值,表示参数节点是否满足以下三个条件之一。
- 参数节点为当前节点。
- 参数节点为当前节点的子节点。
- 参数节点为当前节点的后代节点。
document.body.contains(node)
上面代码检查参数节点
node
,是否包含在当前文档之中。
注意,当前节点传入
contains
方法,返回
true
。
nodeA.contains(nodeA) // true
Node.prototype.compareDocumentPosition()
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 input.compareDocumentPosition(div) // 10
上面代码中,节点
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) { console.log('文档结构正确'); } else { console.log('<body> 不能在 <head> 前面'); }
上面代码中,
compareDocumentPosition
的返回值与
4
(又称掩码)进行与运算(
&
),得到一个布尔值,表示
<head>
是否在
<body>
前面。
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
Node.prototype.normalize()
normalize
方法用于清理当前节点内部的所有文本节点(text)。它会去除空的文本节点,并且将毗邻的文本节点合并成一个,也就是说不存在空的文本节点,以及毗邻的文本节点。
var wrapper = document.createElement('div'); wrapper.appendChild(document.createTextNode('Part 1 ')); wrapper.appendChild(document.createTextNode('Part 2 ')); wrapper.childNodes.length // 2 wrapper.normalize(); wrapper.childNodes.length // 1
上面代码使用
normalize
方法之前,
wrapper
节点有两个毗邻的文本子节点。使用
normalize
方法之后,两个文本子节点被合并成一个。
该方法是
Text.splitText
的逆方法,可以查看《Text 节点对象》一章,了解更多内容。
Node.prototype.getRootNode()
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