JavaScript基礎 —— DOM:遍歷 與 範圍

hozee的個人筆記發表於2019-03-31

遍歷

"DOM2 級遍歷和範圍"模組定義了兩個方法用於輔助完成順序遍歷 DOM 結構的型別: NodeIterator 和 TreeWalker 。這兩個型別能夠基於給定的起點對 DOM 結構執行深度優先(depth-first)的遍歷操作

基本概念

任何節點都可以作為遍歷的根節點。引用《JavaScript高階程式設計(第3版)》的插圖,表示以 document 物件為根節點:

JavaScript基礎 —— DOM:遍歷 與 範圍

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">&nbsp;&nbsp;div1.1</div>
    <div id="div1_2">&nbsp;&nbsp;div1.2</div>
  </div>
  <div id="div2">
    div2
    <p>&nbsp;&nbsp;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) :







相關文章