最近找工作,看到有一道面試題是這樣的:輸出一個頁面中用到的標簽的數組。方法倒是挺多,我首先想到的就是Dom遍歷,上網看了看,大家實現Dom遍歷用到的基本上都是Dom2的方法:NodeIterator和TreeWalker,我試着以不同的角度談談Dom遍歷。
一、廣度優先遍歷(BFS)
廣度優先遍歷的思路定義一個隊列,用來存放子節點,以先進先出的特性,將pop出去的節點的子節點push進去,以此循環,達到BFS
1 function DomBFS(element, callback) { 2 var queue = []; //存放子節點的隊列 3 while(element) { 4 callback(element); 5 if(element.children.length !== 0) { 6 for (var i = 0; i < element.children.length; i++) { 7 queue.push(element.children[i]);//存放子節點 8 } 9 } 10 element = queue.shift(); //取出第一項 11 } 12 }
舉個例子,一個DOM結構是這樣的
1 <div id="root"> 2 <div> 3 <span> 4 <p></p> 5 </span> 6 </div> 7 <p> 8 <span></span> 9 </p> 10 <h1></h1> 11 </div>
1 var arr = [];//存放標簽名 2 var root = document.getElementById("root"); 3 DomBFS(root, function(element) { 4 arr.push(element.tagName); 5 }) 6 console.log(arr);
結果為:
二、深度優先遍歷(DFS)
深度優先遍歷的非遞歸實現:
DFS和BFS的思路差不多,不同的是這里定義了一個堆棧存放子節點,以先進后出的特性,與隊列不同的是,這里需要把第一個子節點放在堆棧最上面,這樣把它pop后,再把它的子節點push進去,依次循環,達到DFS
1 function DomDFS(element, callback) { 2 var stack = [];//存放子節點的棧 3 while (element) { 4 callback(element); 5 if(element.children.length !== 0) { 6 for (var i = element.children.length - 1; i >= 0; i--) {//將最后的子節點先壓進堆棧 7 stack.push(element.children[i]); 8 } 9 } 10 element = stack.pop(); 11 } 12 }
還用上面的例子,結果為:
深度優先遍歷的遞歸實現:
1 function DomDFS(element, callback) { 2 callback(element); 3 element = element.firstElementChild; 4 while(element) { 5 DomDFS(element, callback); 6 element = element.nextElementSibling; 7 } 8 }
三、ES6生成器遍歷DOM樹
廣度優先遍歷和深度優先遍歷都是我們一直在用的方法,但是也會有一點問題,就是firstElementChild在IE678下不兼容,可以用children[0],詳細看這里
ES6中新增的一種函數:生成器函數。用這個方法來實現遍歷DOM樹就很明了。如果不理解生成器,看這里。
1 function* DomTraversal (element) { 2 yield element; 3 element = element.firstElementChild; 4 while(element) { 5 yield* DomTraversal(element); 6 element = element.nextElementSibling; 7 } 8 }
雖然說和遞歸方法很相似,但它的機制卻比遞歸簡單的多。
不同於在下一層遞歸處理每個訪問過的節點子樹,這種方法可以為每個訪問過的節點創建一個生成器並將執行權交給它,從而能夠以迭代的方式書寫類似遞歸的代碼。
並且以一個ES6新增的for-of循環就可以處理生成的節點。
上面的例子:
1 var arr = []; 2 var root = document.getElementById("root"); 3 for (let element of DomTraversal(root)) { 4 arr.push(element.tagName); 5 } 6 console.log(arr);
結果和DFS一樣