# Text 節點和 DocumentFragment 節點
## Text 節點的概念
文本節點(`Text`)代表元素節點(`Element`)和屬性節點(`Attribute`)的文本內容。如果一個節點只包含一段文本,那么它就有一個文本子節點,代表該節點的文本內容。
通常我們使用父節點的`firstChild`、`nextSibling`等屬性獲取文本節點,或者使用`Document`節點的`createTextNode`方法創造一個文本節點。
```javascript
// 獲取文本節點
var textNode = document.querySelector('p').firstChild;
// 創造文本節點
var textNode = document.createTextNode('Hi');
document.querySelector('div').appendChild(textNode);
```
瀏覽器原生提供一個`Text`構造函數。它返回一個文本節點實例。它的參數就是該文本節點的文本內容。
```javascript
// 空字符串
var text1 = new Text();
// 非空字符串
var text2 = new Text('This is a text node');
```
注意,由于空格也是一個字符,所以哪怕只有一個空格,也會形成文本節點。比如,`<p> </p>`包含一個空格,它的子節點就是一個文本節點。
文本節點除了繼承`Node`接口,還繼承了`CharacterData`接口。`Node`接口的屬性和方法請參考《Node 接口》一章,這里不再重復介紹了,以下的屬性和方法大部分來自`CharacterData`接口。
## Text 節點的屬性
### data
`data`屬性等同于`nodeValue`屬性,用來設置或讀取文本節點的內容。
```javascript
// 讀取文本內容
document.querySelector('p').firstChild.data
// 等同于
document.querySelector('p').firstChild.nodeValue
// 設置文本內容
document.querySelector('p').firstChild.data = 'Hello World';
```
### wholeText
`wholeText`屬性將當前文本節點與毗鄰的文本節點,作為一個整體返回。大多數情況下,`wholeText`屬性的返回值,與`data`屬性和`textContent`屬性相同。但是,某些特殊情況會有差異。
舉例來說,HTML 代碼如下。
```html
<p id="para">A <em>B</em> C</p>
```
這時,文本節點的`wholeText`屬性和`data`屬性,返回值相同。
```javascript
var el = document.getElementById('para');
el.firstChild.wholeText // "A "
el.firstChild.data // "A "
```
但是,一旦移除`<em>`節點,`wholeText`屬性與`data`屬性就會有差異,因為這時其實`<p>`節點下面包含了兩個毗鄰的文本節點。
```javascript
el.removeChild(para.childNodes[1]);
el.firstChild.wholeText // "A C"
el.firstChild.data // "A "
```
### length
`length`屬性返回當前文本節點的文本長度。
```javascript
(new Text('Hello')).length // 5
```
### nextElementSibling,previousElementSibling
`nextElementSibling`屬性返回緊跟在當前文本節點后面的那個同級元素節點。如果取不到元素節點,則返回`null`。
```javascript
// HTML 為
// <div>Hello <em>World</em></div>
var tn = document.querySelector('div').firstChild;
tn.nextElementSibling
// <em>World</em>
```
`previousElementSibling`屬性返回當前文本節點前面最近的同級元素節點。如果取不到元素節點,則返回`null:`。
## Text 節點的方法
### appendData(),deleteData(),insertData(),replaceData(),subStringData()
以下5個方法都是編輯`Text`節點文本內容的方法。
- `appendData()`:在`Text`節點尾部追加字符串。
- `deleteData()`:刪除`Text`節點內部的子字符串,第一個參數為子字符串開始位置,第二個參數為子字符串長度。
- `insertData()`:在`Text`節點插入字符串,第一個參數為插入位置,第二個參數為插入的子字符串。
- `replaceData()`:用于替換文本,第一個參數為替換開始位置,第二個參數為需要被替換掉的長度,第三個參數為新加入的字符串。
- `subStringData()`:用于獲取子字符串,第一個參數為子字符串在`Text`節點中的開始位置,第二個參數為子字符串長度。
```javascript
// HTML 代碼為
// <p>Hello World</p>
var pElementText = document.querySelector('p').firstChild;
pElementText.appendData('!');
// 頁面顯示 Hello World!
pElementText.deleteData(7, 5);
// 頁面顯示 Hello W
pElementText.insertData(7, 'Hello ');
// 頁面顯示 Hello WHello
pElementText.replaceData(7, 5, 'World');
// 頁面顯示 Hello WWorld
pElementText.substringData(7, 10);
// 頁面顯示不變,返回"World "
```
### remove()
`remove`方法用于移除當前`Text`節點。
```javascript
// HTML 代碼為
// <p>Hello World</p>
document.querySelector('p').firstChild.remove()
// 現在 HTML 代碼為
// <p></p>
```
### splitText()
`splitText`方法將`Text`節點一分為二,變成兩個毗鄰的`Text`節點。它的參數就是分割位置(從零開始),分割到該位置的字符前結束。如果分割位置不存在,將報錯。
分割后,該方法返回分割位置后方的字符串,而原`Text`節點變成只包含分割位置前方的字符串。
```javascript
// html 代碼為 <p id="p">foobar</p>
var p = document.getElementById('p');
var textnode = p.firstChild;
var newText = textnode.splitText(3);
newText // "bar"
textnode // "foo"
```
父元素節點的`normalize`方法可以將毗鄰的兩個`Text`節點合并。
接上面的例子,文本節點的`splitText`方法將一個`Text`節點分割成兩個,父元素的`normalize`方法可以實現逆操作,將它們合并。
```javascript
p.childNodes.length // 2
// 將毗鄰的兩個 Text 節點合并
p.normalize();
p.childNodes.length // 1
```
## DocumentFragment 節點
`DocumentFragment`節點代表一個文檔的片段,本身就是一個完整的 DOM 樹形結構。它沒有父節點,`parentNode`返回`null`,但是可以插入任意數量的子節點。它不屬于當前文檔,操作`DocumentFragment`節點,要比直接操作 DOM 樹快得多。
它一般用于構建一個 DOM 結構,然后插入當前文檔。`document.createDocumentFragment`方法,以及瀏覽器原生的`DocumentFragment`構造函數,可以創建一個空的`DocumentFragment`節點。然后再使用其他 DOM 方法,向其添加子節點。
```javascript
var docFrag = document.createDocumentFragment();
// 等同于
var docFrag = new DocumentFragment();
var li = document.createElement('li');
li.textContent = 'Hello World';
docFrag.appendChild(li);
document.querySelector('ul').appendChild(docFrag);
```
上面代碼創建了一個`DocumentFragment`節點,然后將一個`li`節點添加在它里面,最后將`DocumentFragment`節點移動到原文檔。
注意,`DocumentFragment`節點本身不能被插入當前文檔。當它作為`appendChild()`、`insertBefore()`、`replaceChild()`等方法的參數時,是它的所有子節點插入當前文檔,而不是它自身。一旦`DocumentFragment`節點被添加進當前文檔,它自身就變成了空節點(`textContent`屬性為空字符串),可以被再次使用。如果想要保存`DocumentFragment`節點的內容,可以使用`cloneNode`方法。
```javascript
document
.querySelector('ul')
.appendChild(docFrag.cloneNode(true));
```
上面這樣添加`DocumentFragment`節點進入當前文檔,不會清空`DocumentFragment`節點。
下面是一個例子,使用`DocumentFragment`反轉一個指定節點的所有子節點的順序。
```javascript
function reverse(n) {
var f = document.createDocumentFragment();
while(n.lastChild) f.appendChild(n.lastChild);
n.appendChild(f);
}
```
`DocumentFragment`節點對象沒有自己的屬性和方法,全部繼承自`Node`節點和`ParentNode`接口。也就是說,`DocumentFragment`節點比`Node`節點多出以下四個屬性。
- `children`:返回一個動態的`HTMLCollection`集合對象,包括當前`DocumentFragment`對象的所有子元素節點。
- `firstElementChild`:返回當前`DocumentFragment`對象的第一個子元素節點,如果沒有則返回`null`。
- `lastElementChild`:返回當前`DocumentFragment`對象的最后一個子元素節點,如果沒有則返回`null`。
- `childElementCount`:返回當前`DocumentFragment`對象的所有子元素數量。
- 前言
- 入門篇
- 導論
- 歷史
- 基本語法
- 數據類型
- 概述
- 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