所有 DOM 節(jié)點對象都繼承了 Node 接口,擁有一些共同的屬性和方法。這是 DOM 操作的基礎(chǔ)。
nodeType
屬性返回一個整數(shù)值,表示節(jié)點的類型。
document.nodeType // 9
上面代碼中,文檔節(jié)點的類型值為9。
Node 對象定義了幾個常量,對應(yīng)這些類型值。
document.nodeType === Node.DOCUMENT_NODE // true
上面代碼中,文檔節(jié)點的nodeType
屬性等于常量Node.DOCUMENT_NODE
。
不同節(jié)點的nodeType
屬性值和對應(yīng)的常量如下。
Node.DOCUMENT_NODE
Node.ELEMENT_NODE
Node.ATTRIBUTE_NODE
Node.TEXT_NODE
Node.DOCUMENT_FRAGMENT_NODE
Node.DOCUMENT_TYPE_NODE
Node.COMMENT_NODE
確定節(jié)點類型時,使用nodeType
屬性是常用方法。
var node = document.documentElement.firstChild;
if (node.nodeType === Node.ELEMENT_NODE) {
console.log('該節(jié)點是元素節(jié)點');
}
nodeName
屬性返回節(jié)點的名稱。
// HTML 代碼如下
// <div id="d1">hello world</div>
var div = document.getElementById('d1');
div.nodeName // "DIV"
上面代碼中,元素節(jié)點<div>
的nodeName
屬性就是大寫的標簽名DIV
。
不同節(jié)點的nodeName
屬性值如下。
#document
#text
#document-fragment
#comment
nodeValue
屬性返回一個字符串,表示當前節(jié)點本身的文本值,該屬性可讀寫。
只有文本節(jié)點(text)、注釋節(jié)點(comment)和屬性節(jié)點(attr)有文本值,因此這三類節(jié)點的nodeValue
可以返回結(jié)果,其他類型的節(jié)點一律返回null
。同樣的,也只有這三類節(jié)點可以設(shè)置nodeValue
屬性的值,其他類型的節(jié)點設(shè)置無效。
// HTML 代碼如下
// <div id="d1">hello world</div>
var div = document.getElementById('d1');
div.nodeValue // null
div.firstChild.nodeValue // "hello world"
上面代碼中,div
是元素節(jié)點,nodeValue
屬性返回null
。div.firstChild
是文本節(jié)點,所以可以返回文本值。
textContent
屬性返回當前節(jié)點和它的所有后代節(jié)點的文本內(nèi)容。
// HTML 代碼為
// <div id="divA">This is <span>some</span> text</div>
document.getElementById('divA').textContent
// This is some text
textContent
屬性自動忽略當前節(jié)點內(nèi)部的 HTML 標簽,返回所有文本內(nèi)容。
該屬性是可讀寫的,設(shè)置該屬性的值,會用一個新的文本節(jié)點,替換所有原來的子節(jié)點。它還有一個好處,就是自動對 HTML 標簽轉(zhuǎn)義。這很適合用于用戶提供的內(nèi)容。
document.getElementById('foo').textContent = '<p>GoodBye!</p>';
上面代碼在插入文本時,會將<p>
標簽解釋為文本,而不會當作標簽處理。
對于文本節(jié)點(text)、注釋節(jié)點(comment)和屬性節(jié)點(attr),textContent
屬性的值與nodeValue
屬性相同。對于其他類型的節(jié)點,該屬性會將每個子節(jié)點(不包括注釋節(jié)點)的內(nèi)容連接在一起返回。如果一個節(jié)點沒有子節(jié)點,則返回空字符串。
文檔節(jié)點(document)和文檔類型節(jié)點(doctype)的textContent
屬性為null
。如果要讀取整個文檔的內(nèi)容,可以使用document.documentElement.textContent
。
baseURI
屬性返回一個字符串,表示當前網(wǎng)頁的絕對路徑。瀏覽器根據(jù)這個屬性,計算網(wǎng)頁上的相對路徑的 URL。該屬性為只讀。
// 當前網(wǎng)頁的網(wǎng)址為
// http://www.example.com/index.html
document.baseURI
// "http://www.example.com/index.html"
如果無法讀到網(wǎng)頁的 URL,baseURI
屬性返回null
。
該屬性的值一般由當前網(wǎng)址的 URL(即window.location
屬性)決定,但是可以使用 HTML 的<base>
標簽,改變該屬性的值。
<base rel="external nofollow" target="_blank" >
設(shè)置了以后,baseURI
屬性就返回<base>
標簽設(shè)置的值。
Node.ownerDocument
屬性返回當前節(jié)點所在的頂層文檔對象,即document
對象。
var d = p.ownerDocument;
d === document // true
document
對象本身的ownerDocument
屬性,返回null
。
Node.nextSibling
屬性返回緊跟在當前節(jié)點后面的第一個同級節(jié)點。如果當前節(jié)點后面沒有同級節(jié)點,則返回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
后面的同級節(jié)點d2
。
注意,該屬性還包括文本節(jié)點和注釋節(jié)點(<!-- comment -->
)。因此如果當前節(jié)點后面有空格,該屬性會返回一個文本節(jié)點,內(nèi)容為空格。
nextSibling
屬性可以用來遍歷所有子節(jié)點。
var el = document.getElementById('div1').firstChild;
while (el !== null) {
console.log(el.nodeName);
el = el.nextSibling;
}
上面代碼遍歷div1
節(jié)點的所有子節(jié)點。
previousSibling
屬性返回當前節(jié)點前面的、距離最近的一個同級節(jié)點。如果當前節(jié)點前面沒有同級節(jié)點,則返回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
前面的同級節(jié)點d1
。
注意,該屬性還包括文本節(jié)點和注釋節(jié)點。因此如果當前節(jié)點前面有空格,該屬性會返回一個文本節(jié)點,內(nèi)容為空格。
parentNode
屬性返回當前節(jié)點的父節(jié)點。對于一個節(jié)點來說,它的父節(jié)點只可能是三種類型:元素節(jié)點(element)、文檔節(jié)點(document)和文檔片段節(jié)點(documentfragment)。
if (node.parentNode) {
node.parentNode.removeChild(node);
}
上面代碼中,通過node.parentNode
屬性將node
節(jié)點從文檔里面移除。
文檔節(jié)點(document)和文檔片段節(jié)點(documentfragment)的父節(jié)點都是null
。另外,對于那些生成后還沒插入 DOM 樹的節(jié)點,父節(jié)點也是null
。
parentElement
屬性返回當前節(jié)點的父元素節(jié)點。如果當前節(jié)點沒有父節(jié)點,或者父節(jié)點類型不是元素節(jié)點,則返回null
。
if (node.parentElement) {
node.parentElement.style.color = 'red';
}
上面代碼中,父元素節(jié)點的樣式設(shè)定了紅色。
由于父節(jié)點只可能是三種類型:元素節(jié)點、文檔節(jié)點(document)和文檔片段節(jié)點(documentfragment)。parentElement
屬性相當于把后兩種父節(jié)點都排除了。
firstChild
屬性返回當前節(jié)點的第一個子節(jié)點,如果當前節(jié)點沒有子節(jié)點,則返回null
。
// HTML 代碼如下
// <p id="p1"><span>First span</span></p>
var p1 = document.getElementById('p1');
p1.firstChild.nodeName // "SPAN"
上面代碼中,p
元素的第一個子節(jié)點是span
元素。
注意,firstChild
返回的除了元素節(jié)點,還可能是文本節(jié)點或注釋節(jié)點。
// HTML 代碼如下
// <p id="p1">
// <span>First span</span>
// </p>
var p1 = document.getElementById('p1');
p1.firstChild.nodeName // "#text"
上面代碼中,p
元素與span
元素之間有空白字符,這導致firstChild
返回的是文本節(jié)點。
lastChild
屬性返回當前節(jié)點的最后一個子節(jié)點,如果當前節(jié)點沒有子節(jié)點,則返回null
。用法與firstChild
屬性相同。
childNodes
屬性返回一個類似數(shù)組的對象(NodeList
集合),成員包括當前節(jié)點的所有子節(jié)點。
var children = document.querySelector('ul').childNodes;
上面代碼中,children
就是ul
元素的所有子節(jié)點。
使用該屬性,可以遍歷某個節(jié)點的所有子節(jié)點。
var div = document.getElementById('div1');
var children = div.childNodes;
for (var i = 0; i < children.length; i++) {
// ...
}
文檔節(jié)點(document)就有兩個子節(jié)點:文檔類型節(jié)點(docType)和 HTML 根元素節(jié)點。
var children = document.childNodes;
for (var i = 0; i < children.length; i++) {
console.log(children[i].nodeType);
}
// 10
// 1
上面代碼中,文檔節(jié)點的第一個子節(jié)點的類型是10(即文檔類型節(jié)點),第二個子節(jié)點的類型是1(即元素節(jié)點)。
注意,除了元素節(jié)點,childNodes
屬性的返回值還包括文本節(jié)點和注釋節(jié)點。如果當前節(jié)點不包括任何子節(jié)點,則返回一個空的NodeList
集合。由于NodeList
對象是一個動態(tài)集合,一旦子節(jié)點發(fā)生變化,立刻會反映在返回結(jié)果之中。
isConnected
屬性返回一個布爾值,表示當前節(jié)點是否在文檔之中。
var test = document.createElement('p');
test.isConnected // false
document.body.appendChild(test);
test.isConnected // true
上面代碼中,test
節(jié)點是腳本生成的節(jié)點,沒有插入文檔之前,isConnected
屬性返回false
,插入之后返回true
。
appendChild()
方法接受一個節(jié)點對象作為參數(shù),將其作為最后一個子節(jié)點,插入當前節(jié)點。該方法的返回值就是插入文檔的子節(jié)點。
var p = document.createElement('p');
document.body.appendChild(p);
上面代碼新建一個<p>
節(jié)點,將其插入document.body
的尾部。
如果參數(shù)節(jié)點是 DOM 已經(jīng)存在的節(jié)點,appendChild()
方法會將其從原來的位置,移動到新位置。
var div = document.getElementById('myDiv');
document.body.appendChild(div);
上面代碼中,插入的是一個已經(jīng)存在的節(jié)點myDiv
,結(jié)果就是該節(jié)點會從原來的位置,移動到document.body
的尾部。
如果appendChild()
方法的參數(shù)是DocumentFragment
節(jié)點,那么插入的是DocumentFragment
的所有子節(jié)點,而不是DocumentFragment
節(jié)點本身。返回值是一個空的DocumentFragment
節(jié)點。
hasChildNodes
方法返回一個布爾值,表示當前節(jié)點是否有子節(jié)點。
var foo = document.getElementById('foo');
if (foo.hasChildNodes()) {
foo.removeChild(foo.childNodes[0]);
}
上面代碼表示,如果foo
節(jié)點有子節(jié)點,就移除第一個子節(jié)點。
注意,子節(jié)點包括所有類型的節(jié)點,并不僅僅是元素節(jié)點。哪怕節(jié)點只包含一個空格,hasChildNodes
方法也會返回true
。
判斷一個節(jié)點有沒有子節(jié)點,有許多種方法,下面是其中的三種。
node.hasChildNodes()
node.firstChild !== null
node.childNodes && node.childNodes.length > 0
hasChildNodes
方法結(jié)合firstChild
屬性和nextSibling
屬性,可以遍歷當前節(jié)點的所有后代節(jié)點。
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
函數(shù)的第一個參數(shù)是某個指定的節(jié)點,第二個參數(shù)是回調(diào)函數(shù)。這個回調(diào)函數(shù)會依次作用于指定節(jié)點,以及指定節(jié)點的所有后代節(jié)點。
cloneNode
方法用于克隆一個節(jié)點。它接受一個布爾值作為參數(shù),表示是否同時克隆子節(jié)點。它的返回值是一個克隆出來的新節(jié)點。
var cloneUL = document.querySelector('ul').cloneNode(true);
該方法有一些使用注意點。
(1)克隆一個節(jié)點,會拷貝該節(jié)點的所有屬性,但是會喪失addEventListener
方法和on-
屬性(即node.onclick = fn
),添加在這個節(jié)點上的事件回調(diào)函數(shù)。
(2)該方法返回的節(jié)點不在文檔之中,即沒有任何父節(jié)點,必須使用諸如Node.appendChild
這樣的方法添加到文檔之中。
(3)克隆一個節(jié)點之后,DOM 有可能出現(xiàn)兩個有相同id
屬性(即id="xxx"
)的網(wǎng)頁元素,這時應(yīng)該修改其中一個元素的id
屬性。如果原節(jié)點有name
屬性,可能也需要修改。
insertBefore
方法用于將某個節(jié)點插入父節(jié)點內(nèi)部的指定位置。
var insertedNode = parentNode.insertBefore(newNode, referenceNode);
insertBefore
方法接受兩個參數(shù),第一個參數(shù)是所要插入的節(jié)點newNode
,第二個參數(shù)是父節(jié)點parentNode
內(nèi)部的一個子節(jié)點referenceNode
。newNode
將插在referenceNode
這個子節(jié)點的前面。返回值是插入的新節(jié)點newNode
。
var p = document.createElement('p');
document.body.insertBefore(p, document.body.firstChild);
上面代碼中,新建一個<p>
節(jié)點,插在document.body.firstChild
的前面,也就是成為document.body
的第一個子節(jié)點。
如果insertBefore
方法的第二個參數(shù)為null
,則新節(jié)點將插在當前節(jié)點內(nèi)部的最后位置,即變成最后一個子節(jié)點。
var p = document.createElement('p');
document.body.insertBefore(p, null);
上面代碼中,p
將成為document.body
的最后一個子節(jié)點。這也說明insertBefore
的第二個參數(shù)不能省略。
注意,如果所要插入的節(jié)點是當前 DOM 現(xiàn)有的節(jié)點,則該節(jié)點將從原有的位置移除,插入新的位置。
由于不存在insertAfter
方法,如果新節(jié)點要插在父節(jié)點的某個子節(jié)點后面,可以用insertBefore
方法結(jié)合nextSibling
屬性模擬。
parent.insertBefore(s1, s2.nextSibling);
上面代碼中,parent
是父節(jié)點,s1
是一個全新的節(jié)點,s2
是可以將s1
節(jié)點,插在s2
節(jié)點的后面。如果s2
是當前節(jié)點的最后一個子節(jié)點,則s2.nextSibling
返回null
,這時s1
節(jié)點會插在當前節(jié)點的最后,變成當前節(jié)點的最后一個子節(jié)點,等于緊跟在s2
的后面。
如果要插入的節(jié)點是DocumentFragment
類型,那么插入的將是DocumentFragment
的所有子節(jié)點,而不是DocumentFragment
節(jié)點本身。返回值將是一個空的DocumentFragment
節(jié)點。
removeChild
方法接受一個子節(jié)點作為參數(shù),用于從當前節(jié)點移除該子節(jié)點。返回值是移除的子節(jié)點。
var divA = document.getElementById('A');
divA.parentNode.removeChild(divA);
上面代碼移除了divA
節(jié)點。注意,這個方法是在divA
的父節(jié)點上調(diào)用的,不是在divA
上調(diào)用的。
下面是如何移除當前節(jié)點的所有子節(jié)點。
var element = document.getElementById('top');
while (element.firstChild) {
element.removeChild(element.firstChild);
}
被移除的節(jié)點依然存在于內(nèi)存之中,但不再是 DOM 的一部分。所以,一個節(jié)點移除以后,依然可以使用它,比如插入到另一個節(jié)點下面。
如果參數(shù)節(jié)點不是當前節(jié)點的子節(jié)點,removeChild
方法將報錯。
replaceChild
方法用于將一個新的節(jié)點,替換當前節(jié)點的某一個子節(jié)點。
var replacedNode = parentNode.replaceChild(newChild, oldChild);
上面代碼中,replaceChild
方法接受兩個參數(shù),第一個參數(shù)newChild
是用來替換的新節(jié)點,第二個參數(shù)oldChild
是將要替換走的子節(jié)點。返回值是替換走的那個節(jié)點oldChild
。
var divA = document.getElementById('divA');
var newSpan = document.createElement('span');
newSpan.textContent = 'Hello World!';
divA.parentNode.replaceChild(newSpan, divA);
上面代碼是如何將指定節(jié)點divA
替換走。
contains
方法返回一個布爾值,表示參數(shù)節(jié)點是否滿足以下三個條件之一。
document.body.contains(node)
上面代碼檢查參數(shù)節(jié)點node
,是否包含在當前文檔之中。
注意,當前節(jié)點傳入contains
方法,返回true
。
nodeA.contains(nodeA) // true
compareDocumentPosition
方法的用法,與contains
方法完全一致,返回一個六個比特位的二進制值,表示參數(shù)節(jié)點與當前節(jié)點的關(guān)系。
二進制值 | 十進制值 | 含義 |
---|---|---|
000000 | 0 | 兩個節(jié)點相同 |
000001 | 1 | 兩個節(jié)點不在同一個文檔(即有一個節(jié)點不在當前文檔) |
000010 | 2 | 參數(shù)節(jié)點在當前節(jié)點的前面 |
000100 | 4 | 參數(shù)節(jié)點在當前節(jié)點的后面 |
001000 | 8 | 參數(shù)節(jié)點包含當前節(jié)點 |
010000 | 16 | 當前節(jié)點包含參數(shù)節(jié)點 |
100000 | 32 | 瀏覽器內(nèi)部使用 |
// 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
上面代碼中,節(jié)點div
包含節(jié)點input
(二進制010000
),而且節(jié)點input
在節(jié)點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('文檔結(jié)構(gòu)正確');
} else {
console.log('<body> 不能在 <head> 前面');
}
上面代碼中,compareDocumentPosition
的返回值與4
(又稱掩碼)進行與運算(&
),得到一個布爾值,表示<head>
是否在<body>
前面。
isEqualNode
方法返回一個布爾值,用于檢查兩個節(jié)點是否相等。所謂相等的節(jié)點,指的是兩個節(jié)點的類型相同、屬性相同、子節(jié)點相同。
var p1 = document.createElement('p');
var p2 = document.createElement('p');
p1.isEqualNode(p2) // true
isSameNode
方法返回一個布爾值,表示兩個節(jié)點是否為同一個節(jié)點。
var p1 = document.createElement('p');
var p2 = document.createElement('p');
p1.isSameNode(p2) // false
p1.isSameNode(p1) // true
normalize
方法用于清理當前節(jié)點內(nèi)部的所有文本節(jié)點(text)。它會去除空的文本節(jié)點,并且將毗鄰的文本節(jié)點合并成一個,也就是說不存在空的文本節(jié)點,以及毗鄰的文本節(jié)點。
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
節(jié)點有兩個毗鄰的文本子節(jié)點。使用normalize
方法之后,兩個文本子節(jié)點被合并成一個。
該方法是Text.splitText
的逆方法,可以查看《Text 節(jié)點對象》一章,了解更多內(nèi)容。
getRootNode()
方法返回當前節(jié)點所在文檔的根節(jié)點document
,與ownerDocument
屬性的作用相同。
document.body.firstChild.getRootNode() === document
// true
document.body.firstChild.getRootNode() === document.body.firstChild.ownerDocument
// true
該方法可用于document
節(jié)點自身,這一點與document.ownerDocument
不同。
document.getRootNode() // document
document.ownerDocument // null
更多建議: