(JS基礎)DOM:遍歷 與 範圍

遍歷(Iterator)

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

基本概念

任何節點均可以做爲遍歷的根節點。引用《JavaScript高級程序設計(第3版)》的插圖,表示以 document 對象爲根節點:
html


NodeIterator

使用document.createNodeIterator(rootNode, whatToShow, filter, isEntrityReferenceExpansion?)方法能夠建立 NodeIterator 的實例。 4 個參數的意思:
java

  • 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 的實例,該對象主要有兩個方法:node

  • 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 樹內隨意"遊走"。
ui


範圍(Range)

"DOM2 級遍歷和範圍"模塊定義"範圍"(range)接口。經過範圍能夠選擇文檔中的一個區域,而沒必要考慮節點的界限。使用document.createRange()方法能夠建立 range 對象。
spa

屬性

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 對象的範圍,無返回值。
code

  • 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()複製 Range 對象的內容返回 DOM 對象可用於插入到文檔的其餘地方。
  • insertNode(node)向範圍選區的開始插入一個節點,對 DOM 產生相同影響。
  • surroundContents(node)向範圍選區環繞插入節點,對 DOM 產生相同影響。舉個例,有<div>hello <b>world</b></div>的 HTML 文檔片斷,範圍選擇"llo <b>world</b>",使用document.createElement('p')建立一個用於環繞的 node 節點對象,而後使用本方法,原 HTML 文檔片斷將變爲<div>he<p>llo <b>world</b></p></div>。須要注意的是,在使用本方法前,對 "node" 對象插入其餘節點是不會有效果的;但爲 "node" 對象添加特性是有效果的。本方法執行時,後臺會執行以下步驟:
    1. 提取出範圍中的內容( 相似執行 extractContent() );
    2. 將給定節點插入到文檔中原來範圍所在的位置上(忽略節點內容);
    3. 將提取的文檔片斷的內容添加到給定節點中。
  • collapse(isStart) :將範圍摺疊,isStart 表示是否摺疊到起點。不會對 DOM 產生影響,屬於一個沒啥用的方法。摺疊後的範圍,起點和終點處於同一個位置。與此有關係的一個 Range 對象的屬性:
    • collapsed是否摺疊完成。用於判斷兩個節點是否緊密相鄰。舉個例,有這樣一個 HTML 文檔片斷<p id="p1">p1</p><p id="p2">p2</p>,分別獲取"p1"、"p2"節點對象,建立range對象設定起點終點range.setStartAfter(p1); range.setEndBefore(p2),由於"p1"和"p2"是相鄰節點,因此range.collapsed的值爲true
  • compareBoundaryPoints(type, range)比較與 range 範圍是否有公共邊界(起點或終點),用於判斷兩個範圍的位置關係。方法有 3 種可能的返回值,0 表示二者位置相同-1 表示自身的點在前1 則表示自身的點在後。range 表示與自身比較的範圍對象;type 表示比較方式的常量,常量值以下:
    • Range.START_TO_START:0,比較二者的起點位置。
    • Range.START_TO_END:1,比較自身的起點目標的終點位置。
    • Range.END_TO_END:2,比較二者的終點位置。
    • Range.END_TO_START:3,比較自身的終點目標的起點位置。
  • cloneRange()複製範圍副本,返回的對象仍然是 Range 對象。要與cloneContents()方法區分。
  • detach() :將 Range 對象從建立範圍的文檔中分離出該範圍。(實測測試,該方法並無什麼用,以後調用deleteContents()方法仍然會刪除 DOM。)
相關文章
相關標籤/搜索