遍歷
"DOM2 級遍歷和範圍"模組定義了兩個方法用於輔助完成順序遍歷 DOM 結構的型別: NodeIterator 和 TreeWalker 。這兩個型別能夠基於給定的起點對 DOM 結構執行深度優先(depth-first)的遍歷操作。
基本概念
任何節點都可以作為遍歷的根節點。引用《JavaScript高階程式設計(第3版)》的插圖,表示以 document 物件為根節點:
NodeIterator
使用document.createNodeIterator(rootNode, whatToShow, filter, isEntrityReferenceExpansion?)
方法可以建立 NodeIterator 的例項。 4 個引數的意思:
- rootNode :表示作為遍歷起點的根節點。
- whatToShow :表示要訪問的節點的"SHOW"常量。"SHOW"常量是位掩碼,位於 NodeFilter 物件內,除了 "SHOW_ALL" 外可以使用按位或操作符來組合多個選項。數字程式碼如下(省略HTML以外的值):
- NodeFilter.SHOW_ALL:所有節點。
- NodeFilter.SHOW_ELEMENT:元素節點。
- NodeFilter.SHOW_ATTRIBUTE:特性節點(由於 DOM 結構,實際上不能使用該值)。
- NodeFilter.SHOW_TEXT:文字節點。
- NodeFilter.SHOW_COMMENT:註釋節點。
- NodeFilter.SHOW_DOCUMENT:文件節點。
- NodeFilter.SHOW_DOCUMENT_TYPE:文件型別節點。
- ...
- filter :傳入一個能返回"FILTER"常量的過濾函式;或傳入一個包含名為"acceptNode"的過濾函式的物件。
- "FILTER"常量也是 NodeFilter 內的屬性,有 3 個:
- NodeFilter.FILTER_ACCEPT:表示通過,即新增該節點物件到迭代器中。
- NodeFilter.FILTER_SKIP:表示忽略,即忽略該節點繼續遍歷。
- NodeFilter.FILTER_REJECT:表示拒絕,與"FILTER_SKIP"效果相同。
- 過濾函式的格式:
// node 為每次遍歷的節點物件 function acceptNode(node) { // 過濾除了 div 以外的元素 return node.nodeName.toLowerCase() === 'div' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP }複製程式碼
- isEntrityReferenceExpansion :布林值,表示是否要擴充套件實體引用(在HTML中無效)。
函式createNodeIterator()
執行後,返回一個 NodeIterator 的例項,該物件主要有兩個方法:
- nextNode():獲取迭代器的下一個節點物件,超出範圍則返回
null
。 - previousNode():獲取迭代器的前一個節點物件,超出範圍同樣返回
null
。
完整例項如下:
<body>
<div class="div1">div1</div>
<div class="div2">
<p>pppppp</p>
text
</div>
</body>複製程式碼
// 過濾的型別選擇
let whatToShow = NodeFilter.SHOW_ALL;
// 過濾器(傳入函式)
let filter = function (node) {
return node.nodeName.toLowerCase() === 'div' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
}
// 過濾器2(傳入物件)
let filter2 = {
acceptNode: filter
}
// 建立迭代器
let iterator = document.createNodeIterator(document.body, whatToShow, filter, false);
// 測試
console.log(iterator.nextNode()); // "<div class="div1">div1</div>"
console.log(iterator.previousNode()); // "<div class="div2">...</div>"
console.log(iterator.nextNode()); // "<div class="div1">div1</div>"
console.log(iterator.nextNode()); // null
複製程式碼
TreeWalker
使用document.createTreeWalker()
方法可以建立 TreeWalker 物件,引數與createNodeIterator()
方法一致。TreeWalker 是 NodeIterator 的一個高階版本,使用方法也基本一致,有一點除外,過濾函式的返回值如果是 "NodeFilter.FILTER_REJECT" ,該節點的子節點都會被跳過。除了包含 nextNode() 和 previousNode() 方法之外,還提供瞭如下屬性/方法:
- parentNode() :遍歷到當前節點的父節點。
- firstChild() :遍歷到當前節點的第一個子節點。
- lasterChild() :遍歷到當前節點的最後一個子節點。
- nextSibling() :遍歷到當前節點的下一個同輩節點。
- previousSibling() :遍歷到當前節點的上一個同輩節點。
- currentNode:表示"walker"當前位置,可讀寫屬性。
<body>
<div id="div1">
div1
<div id="div1_1"> div1.1</div>
<div id="div1_2"> div1.2</div>
</div>
<div id="div2">
div2
<p> div2-p</p>
</div>
</body>複製程式碼
let filter = function (node) {
return node.nodeName.toLowerCase() === 'div' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
}
let walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ALL, filter, false)
// 測試
console.log(walker.nextNode()); // "<div id="div1">...</div>"
console.log(walker.nextSibling()); // "<div id="div2">...</div>"
console.log(walker.previousSibling()); // "<div id="div1">...</div>"
console.log(walker.firstChild()); // "<div id="div1_1">...</div>"
console.log(walker.parentNode()); // "<div id="div1">...</div>"
console.log(walker.lastChild()); // "<div id="div1_2">...</div>"
console.log(walker.nextNode()); // "<div id="div2">...</div>"複製程式碼
需要注意的是,"walker" 只能在 TreeWalker 物件內"遊走",上面幾個方法也是針對已過濾的結果的相對位置"遊走",即使"div"內有"Text"型別的節點。除非建立 TreeWalker 物件的第一第二個引數分別傳入 document 和 null ,就可以在 DOM 樹內隨意"遊走"。
範圍
"DOM2 級遍歷和範圍"模組定義"範圍"(range)介面。通過範圍可以選擇文件中的一個區域,而不必考慮節點的界限。使用document.createRange()
方法可以建立 range 物件。
屬性
Range 物件的屬性提供了當前範圍在文件中的位置資訊。
- startContainer :包含範圍起點的節點(即選區中起點節點的父節點)。
- startOffset :起點節點在 startContainer 中的偏移量。當 startContainer 是文字、註釋、CDATA節點,表示起點跳過的字元數量,即
<p>hello</p>
中被選擇了"llo",本屬性值為 3 ;否則,表示起點節點在父節點的 childNodes 是第幾個,即通過range.startContainer.childNodes[range.startOffset - 1]
可訪問起點節點。
- endContainer :包含範圍終點的節點(即選區中結尾節點的父節點)。
- endOffset :終點節點在 endContainer 中的偏移量。(與 startOffset 的規則相同)
- commonAncestorContainer :startContainer 和 endContainer 共同的最近的祖先節點。
從 DOM 中選擇範圍(方法)
以下方法均用於修改 Range 物件的範圍,無返回值。
- selectNode(node) :選擇整個節點,包括其子節點。
- selectNodeContents(node) :只選擇節點的子節點,不包括自身。
- setStartBefore(refNode) :將範圍的起點設定在 refNode 之前,即 refNode 是範圍選區中的起始節點。
- setStartAfter(refNode) :將範圍的起點設定在 refNode 之後,即 refNode 的下一個同輩節點是範圍選區中的起始節點。
- setEndBefore(refNode) :將範圍的終點設定在 refNode 之前,即 refNode 是的前一個同輩節點範圍選區中的終點節點。
- setStartAfter(refNode) :將範圍的起點設定在 refNode 之後,即 refNode 是範圍選區中的終點節點。
- setStart(startContainer , startOffset) :設定起點,當 startContainer 為文字、註釋、CDATA節點時,可以選擇其中的一部分作為起點節點,例如
<p>hello</p>
選擇了"llo</p>"時,會自動將其補全,即起點節點為<p>llo</p>
。可以看成快速設定指定屬性。 - setEnd(endContainer , endOffset) :設定終點,與 setStart 方法類似。
操作 DOM 範圍中的內容(方法)
使用以下方法,可以對 DOM 刪除或插入。
- deleteContents() :從文件中刪除該範圍所包含的內容。由於範圍選區在修改底層 DOM 結構時能夠保證格式良好,所以即使內容被刪除,DOM 結構依然是良好的。若範圍包含部分的文字、註釋、CDATA節點,則剩下的節點會被"包裹"成完成的節點,即
<p>hello</p>
被刪除了"llo</p>",則剩下的節點會變成<p>he</p>
,還是完整的節點。 - extractContents() :與 deleteContents() 類似,但返回被刪除的範圍,即剪下該範圍,用於插入到文件的其他地方。
- cloneContents() :建立範圍物件的 DOM 副本,即克隆該範圍,用於插入到文件的其他地方。
- insertNode(node) :向範圍選區的開始處插入一個節點,對 DOM 產生相同影響。
- surroundContents(node) :