玩轉DOM遍歷——用NodeIterator實現getElementById,getElementsByTagName方法

先聲明一下DOM2中NodeIterator和TreeWalker這兩類型真的只是用來玩玩的,由於性能不行遍歷起來超級慢,在JS中基本用不到它們,除了《高程》上有兩三頁對它的講解外,谷歌的學習資料也是甚少(卻是有挺多國外文章)...因爲本着不放過任何知識的態度,結合着本身的理解學習了下這兩玩意,大家對這兩東西瞭解瞭解就好~

DOM2級遍歷和範圍模塊定義了兩個用於完成順序遍歷DOM結構的類型:NodeIterator和TreeWalker。這兩類型基於給定起點對DOM結構執行深度優先先序遍歷,兼容性>IE8和高版本其餘瀏覽器可訪問。

NodeIterator:javascript

使用document.createNodeIterator(root, whatShow, filter, entityReferenceExpansion)建立NoedIterator類型實例iterator,因此原型鏈關係爲:
iterator.__proto__->NodeIterator.prototype->Object.prototye
html

TreeWalker:java

使用document.createTreeWalker(root, whatShow, filter, entityReferenceExpansion)建立TreeWalker類型實例walker,因此原型鏈關係爲:
walker.__proto__->TreeWalker.prototype->Object.prototype


對比下來,其實就兩個方法經常使用,nextNode()和previousNode()。TreeWalker.prototype比NodeIterator.prototype多了一些在不一樣方向上遍歷的方法也就沒什麼了。
(1).在每一個iterator中有一個內部指針指向根節點,nextNode方法是返回遍歷器內部指針所在節點,而後會將指針移向下一個節點。previousNode()方法是先將指針移向上一個節點,而後返回該節點。因此nextNode()==previousNode()

(2).在每一個walker中也有一個內部指針,可是指向根節點的第一個子節點,nextNode方法是返回遍歷器所在節點而後並不移動指針(就是說指針和節點在同一處),previous()方法是先將指針移向上一個節點,而後返回該節點,因此這裏nextNode() != previous()

因此TreeWalker.prototype就有一個currentNode屬性,表示在上一次遍歷中返回的節點:

(3).區別說完,說說它兩的參數都是相同的:
root:想要做爲搜索起點的樹中的節點
whatToShow:表示要訪問哪些節點的數字代碼,來自NodeFilter.prototype仍是NodeFilter自身上中的這些大寫常量...

filter:是NodeFilter類型實例對象,或者是一個表示應該接受仍是拒絕某種特定節點的函數。做用是當調用nextNode或previousNode時候要通過這個過濾器來刪選想要的節點,若是說文檔中任何一個節點走一步,那麼根據篩選節點類型不一樣每次返回的節點實際上可能走了好幾步。
entityReferenceExpansion:表示是否要擴展實體引用,false就好。

對了,NodeIterator和TreeWalker還有一點區別就是在使用NodeIterator對象時,NodeFilter.FILTER_SKIP和NodeFilter.FILTER_REJECT做用相同跳過指定節點。在使用TreeWalker對象時,NodeFilter.FILTER_SKIP會跳過相應節點繼續前進到子樹中下一個節點,NodeFilter.FILTER_REJECT會相應節點及該節點的整個子樹。


OK!說完了上面的,也不知道你們有沒有懂~不懂不要緊反正這兩類型也不經常使用,效率也差~
經過DOM遍歷的這兩類型很容易讓人想到咱們經常使用的document.getElementById,document.getElementsByTagName,document.getElementsByNames...系列方法不是也是在DOM中搜尋指定節點的麼...這裏用NodeIterator來實現一下node

Document.prototype.getElementById = function(id){
 var filter = function(node){
   return node.id == id ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
  } 
 var iterator = document.createNodeIterator(document, NodeFilter.SHOW_ELEMENT, filter, false);
 var node = iterator.nextNode(); 
 return node;
}


Document.prototype.getElementsByTagName = function(tagname){
 var filter = function(node){
     return node.tagName.toLowerCase() == tagname ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
  }
 var htmlcollection = []; 
 var iterator = document.createNodeIterator(document, NodeFilter.SHOW_ELEMENT, filter, false);
 var node = iterator.nextNode(); 
 while(node!=null){
   htmlcollection.push(node);
   node = iterator.nextNode();
 }
 htmlcollection.__proto__ = HTMLCollection.prototype;
 return htmlcollection;
}


成功!其餘的方法相似的,感興趣能夠自行實現~

stackoverflow裏有人提出了When to use NodeIterator? 把querySelector和filter過濾進行比較,這誰快誰慢光看名字就顯而易見嘛,感興趣能夠看看啊~我粗略測試下

也不知道人家JS引擎中getElementById真正是怎麼實現的,改天抽空看看~


參考瀏覽器

《JavaScript高級程序設計》dom

JavaScript標準參考教程-document節點函數

相關文章
相關標籤/搜索