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


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

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

NodeIterator:

使用document.createNodeIterator(root, whatShow, filter, entityReferenceExpansion)創建NoedIterator類型實例iterator,所以原型鏈關系為:
iterator.__proto__->NodeIterator.prototype->Object.prototye

TreeWalker:

使用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來實現一下

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高級程序設計》

JavaScript標准參考教程-document節點


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM