# Node 接口
所有 DOM 節點對象都繼承了 Node 接口,擁有一些共同的屬性和方法。這是 DOM 操作的基礎。
## 屬性
### Node.prototype.nodeType
`nodeType`屬性返回一個整數值,表示節點的類型。
```javascript
document.nodeType // 9
```
上面代碼中,文檔節點的類型值為9。
Node 對象定義了幾個常量,對應這些類型值。
```javascript
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`屬性是常用方法。
```javascript
var node = document.documentElement.firstChild;
if (node.nodeType === Node.ELEMENT_NODE) {
console.log('該節點是元素節點');
}
```
### Node.prototype.nodeName
`nodeName`屬性返回節點的名稱。
```javascript
// 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`屬性的值,其他類型的節點設置無效。
```javascript
// 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`屬性返回當前節點和它的所有后代節點的文本內容。
```javascript
// HTML 代碼為
// <div id="divA">This is <span>some</span> text</div>
document.getElementById('divA').textContent
// This is some text
```
`textContent`屬性自動忽略當前節點內部的 HTML 標簽,返回所有文本內容。
該屬性是可讀寫的,設置該屬性的值,會用一個新的文本節點,替換所有原來的子節點。它還有一個好處,就是自動對 HTML 標簽轉義。這很適合用于用戶提供的內容。
```javascript
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。該屬性為只讀。
```javascript
// 當前網頁的網址為
// http://www.example.com/index.html
document.baseURI
// "http://www.example.com/index.html"
```
如果無法讀到網頁的 URL,`baseURI`屬性返回`null`。
該屬性的值一般由當前網址的 URL(即`window.location`屬性)決定,但是可以使用 HTML 的`<base>`標簽,改變該屬性的值。
```html
<base href="http://www.example.com/page.html">
```
設置了以后,`baseURI`屬性就返回`<base>`標簽設置的值。
### Node.prototype.ownerDocument
`Node.ownerDocument`屬性返回當前節點所在的頂層文檔對象,即`document`對象。
```javascript
var d = p.ownerDocument;
d === document // true
```
`document`對象本身的`ownerDocument`屬性,返回`null`。
### Node.prototype.nextSibling
`Node.nextSibling`屬性返回緊跟在當前節點后面的第一個同級節點。如果當前節點后面沒有同級節點,則返回`null`。
```javascript
// 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`屬性可以用來遍歷所有子節點。
```javascript
var el = document.getElementById('div1').firstChild;
while (el !== null) {
console.log(el.nodeName);
el = el.nextSibling;
}
```
上面代碼遍歷`div1`節點的所有子節點。
### Node.prototype.previousSibling
`previousSibling`屬性返回當前節點前面的、距離最近的一個同級節點。如果當前節點前面沒有同級節點,則返回`null`。
```javascript
// 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)。
```javascript
if (node.parentNode) {
node.parentNode.removeChild(node);
}
```
上面代碼中,通過`node.parentNode`屬性將`node`節點從文檔里面移除。
文檔節點(document)和文檔片段節點(documentfragment)的父節點都是`null`。另外,對于那些生成后還沒插入 DOM 樹的節點,父節點也是`null`。
### Node.prototype.parentElement
`parentElement`屬性返回當前節點的父元素節點。如果當前節點沒有父節點,或者父節點類型不是元素節點,則返回`null`。
```javascript
if (node.parentElement) {
node.parentElement.style.color = 'red';
}
```
上面代碼中,父元素節點的樣式設定了紅色。
由于父節點只可能是三種類型:元素節點、文檔節點(document)和文檔片段節點(documentfragment)。`parentElement`屬性相當于把后兩種父節點都排除了。
### Node.prototype.firstChild,Node.prototype.lastChild
`firstChild`屬性返回當前節點的第一個子節點,如果當前節點沒有子節點,則返回`null`。
```javascript
// HTML 代碼如下
// <p id="p1"><span>First span</span></p>
var p1 = document.getElementById('p1');
p1.firstChild.nodeName // "SPAN"
```
上面代碼中,`p`元素的第一個子節點是`span`元素。
注意,`firstChild`返回的除了元素節點,還可能是文本節點或注釋節點。
```javascript
// 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`集合),成員包括當前節點的所有子節點。
```javascript
var children = document.querySelector('ul').childNodes;
```
上面代碼中,`children`就是`ul`元素的所有子節點。
使用該屬性,可以遍歷某個節點的所有子節點。
```javascript
var div = document.getElementById('div1');
var children = div.childNodes;
for (var i = 0; i < children.length; i++) {
// ...
}
```
文檔節點(document)就有兩個子節點:文檔類型節點(docType)和 HTML 根元素節點。
```javascript
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`屬性返回一個布爾值,表示當前節點是否在文檔之中。
```javascript
var test = document.createElement('p');
test.isConnected // false
document.body.appendChild(test);
test.isConnected // true
```
上面代碼中,`test`節點是腳本生成的節點,沒有插入文檔之前,`isConnected`屬性返回`false`,插入之后返回`true`。
## 方法
### Node.prototype.appendChild()
`appendChild()`方法接受一個節點對象作為參數,將其作為最后一個子節點,插入當前節點。該方法的返回值就是插入文檔的子節點。
```javascript
var p = document.createElement('p');
document.body.appendChild(p);
```
上面代碼新建一個`<p>`節點,將其插入`document.body`的尾部。
如果參數節點是 DOM 已經存在的節點,`appendChild()`方法會將其從原來的位置,移動到新位置。
```javascript
var div = document.getElementById('myDiv');
document.body.appendChild(div);
```
上面代碼中,插入的是一個已經存在的節點`myDiv`,結果就是該節點會從原來的位置,移動到`document.body`的尾部。
如果`appendChild()`方法的參數是`DocumentFragment`節點,那么插入的是`DocumentFragment`的所有子節點,而不是`DocumentFragment`節點本身。返回值是一個空的`DocumentFragment`節點。
### Node.prototype.hasChildNodes()
`hasChildNodes`方法返回一個布爾值,表示當前節點是否有子節點。
```javascript
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`屬性,可以遍歷當前節點的所有后代節點。
```javascript
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`方法用于克隆一個節點。它接受一個布爾值作為參數,表示是否同時克隆子節點。它的返回值是一個克隆出來的新節點。
```javascript
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`方法用于將某個節點插入父節點內部的指定位置。
```javascript
var insertedNode = parentNode.insertBefore(newNode, referenceNode);
```
`insertBefore`方法接受兩個參數,第一個參數是所要插入的節點`newNode`,第二個參數是父節點`parentNode`內部的一個子節點`referenceNode`。`newNode`將插在`referenceNode`這個子節點的前面。返回值是插入的新節點`newNode`。
```javascript
var p = document.createElement('p');
document.body.insertBefore(p, document.body.firstChild);
```
上面代碼中,新建一個`<p>`節點,插在`document.body.firstChild`的前面,也就是成為`document.body`的第一個子節點。
如果`insertBefore`方法的第二個參數為`null`,則新節點將插在當前節點內部的最后位置,即變成最后一個子節點。
```javascript
var p = document.createElement('p');
document.body.insertBefore(p, null);
```
上面代碼中,`p`將成為`document.body`的最后一個子節點。這也說明`insertBefore`的第二個參數不能省略。
注意,如果所要插入的節點是當前 DOM 現有的節點,則該節點將從原有的位置移除,插入新的位置。
由于不存在`insertAfter`方法,如果新節點要插在父節點的某個子節點后面,可以用`insertBefore`方法結合`nextSibling`屬性模擬。
```javascript
parent.insertBefore(s1, s2.nextSibling);
```
上面代碼中,`parent`是父節點,`s1`是一個全新的節點,`s2`是可以將`s1`節點,插在`s2`節點的后面。如果`s2`是當前節點的最后一個子節點,則`s2.nextSibling`返回`null`,這時`s1`節點會插在當前節點的最后,變成當前節點的最后一個子節點,等于緊跟在`s2`的后面。
如果要插入的節點是`DocumentFragment`類型,那么插入的將是`DocumentFragment`的所有子節點,而不是`DocumentFragment`節點本身。返回值將是一個空的`DocumentFragment`節點。
### Node.prototype.removeChild()
`removeChild`方法接受一個子節點作為參數,用于從當前節點移除該子節點。返回值是移除的子節點。
```javascript
var divA = document.getElementById('A');
divA.parentNode.removeChild(divA);
```
上面代碼移除了`divA`節點。注意,這個方法是在`divA`的父節點上調用的,不是在`divA`上調用的。
下面是如何移除當前節點的所有子節點。
```javascript
var element = document.getElementById('top');
while (element.firstChild) {
element.removeChild(element.firstChild);
}
```
被移除的節點依然存在于內存之中,但不再是 DOM 的一部分。所以,一個節點移除以后,依然可以使用它,比如插入到另一個節點下面。
如果參數節點不是當前節點的子節點,`removeChild`方法將報錯。
### Node.prototype.replaceChild()
`replaceChild`方法用于將一個新的節點,替換當前節點的某一個子節點。
```javascript
var replacedNode = parentNode.replaceChild(newChild, oldChild);
```
上面代碼中,`replaceChild`方法接受兩個參數,第一個參數`newChild`是用來替換的新節點,第二個參數`oldChild`是將要替換走的子節點。返回值是替換走的那個節點`oldChild`。
```javascript
var divA = document.getElementById('divA');
var newSpan = document.createElement('span');
newSpan.textContent = 'Hello World!';
divA.parentNode.replaceChild(newSpan, divA);
```
上面代碼是如何將指定節點`divA`替換走。
### Node.prototype.contains()
`contains`方法返回一個布爾值,表示參數節點是否滿足以下三個條件之一。
- 參數節點為當前節點。
- 參數節點為當前節點的子節點。
- 參數節點為當前節點的后代節點。
```javascript
document.body.contains(node)
```
上面代碼檢查參數節點`node`,是否包含在當前文檔之中。
注意,當前節點傳入`contains`方法,返回`true`。
```javascript
nodeA.contains(nodeA) // true
```
### Node.prototype.compareDocumentPosition()
`compareDocumentPosition`方法的用法,與`contains`方法完全一致,返回一個六個比特位的二進制值,表示參數節點與當前節點的關系。
二進制值 | 十進制值 | 含義
---------|------|-----
000000 | 0 | 兩個節點相同
000001 | 1 | 兩個節點不在同一個文檔(即有一個節點不在當前文檔)
000010 | 2 | 參數節點在當前節點的前面
000100 | 4 | 參數節點在當前節點的后面
001000 | 8 | 參數節點包含當前節點
010000 | 16 | 當前節點包含參數節點
100000 | 32 | 瀏覽器內部使用
```javascript
// 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`返回值的含義,定義在每一個比特位上,所以如果要檢查某一種特定的含義,就需要使用比特位運算符。
```javascript
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`方法返回一個布爾值,用于檢查兩個節點是否相等。所謂相等的節點,指的是兩個節點的類型相同、屬性相同、子節點相同。
```javascript
var p1 = document.createElement('p');
var p2 = document.createElement('p');
p1.isEqualNode(p2) // true
```
`isSameNode`方法返回一個布爾值,表示兩個節點是否為同一個節點。
```javascript
var p1 = document.createElement('p');
var p2 = document.createElement('p');
p1.isSameNode(p2) // false
p1.isSameNode(p1) // true
```
### Node.prototype.normalize()
`normalize`方法用于清理當前節點內部的所有文本節點(text)。它會去除空的文本節點,并且將毗鄰的文本節點合并成一個,也就是說不存在空的文本節點,以及毗鄰的文本節點。
```javascript
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`屬性的作用相同。
```javascript
document.body.firstChild.getRootNode() === document
// true
document.body.firstChild.getRootNode() === document.body.firstChild.ownerDocument
// true
```
該方法可用于`document`節點自身,這一點與`document.ownerDocument`不同。
```javascript
document.getRootNode() // document
document.ownerDocument // null
```
- 前言
- 入門篇
- 導論
- 歷史
- 基本語法
- 數據類型
- 概述
- null,undefined 和布爾值
- 數值
- 字符串
- 對象
- 函數
- 數組
- 運算符
- 算術運算符
- 比較運算符
- 布爾運算符
- 二進制位運算符
- 其他運算符,運算順序
- 語法專題
- 數據類型的轉換
- 錯誤處理機制
- 編程風格
- console 對象與控制臺
- 標準庫
- Object 對象
- 屬性描述對象
- Array 對象
- 包裝對象
- Boolean 對象
- Number 對象
- String 對象
- Math 對象
- Date 對象
- RegExp 對象
- JSON 對象
- 面向對象編程
- 實例對象與 new 命令
- this 關鍵字
- 對象的繼承
- Object 對象的相關方法
- 嚴格模式
- 異步操作
- 概述
- 定時器
- Promise 對象
- DOM
- 概述
- Node 接口
- NodeList 接口,HTMLCollection 接口
- ParentNode 接口,ChildNode 接口
- Document 節點
- Element 節點
- 屬性的操作
- Text 節點和 DocumentFragment 節點
- CSS 操作
- Mutation Observer API
- 事件
- EventTarget 接口
- 事件模型
- Event 對象
- 鼠標事件
- 鍵盤事件
- 進度事件
- 表單事件
- 觸摸事件
- 拖拉事件
- 其他常見事件
- GlobalEventHandlers 接口
- 瀏覽器模型
- 瀏覽器模型概述
- window 對象
- Navigator 對象,Screen 對象
- Cookie
- XMLHttpRequest 對象
- 同源限制
- CORS 通信
- Storage 接口
- History 對象
- Location 對象,URL 對象,URLSearchParams 對象
- ArrayBuffer 對象,Blob 對象
- File 對象,FileList 對象,FileReader 對象
- 表單,FormData 對象
- IndexedDB API
- Web Worker
- 附錄:網頁元素接口
- a
- img
- form
- input
- button
- option
- video,audio