最近找工作,看到有一道面试题是这样的:输出一个页面中用到的标签的数组。方法倒是挺多,我首先想到的就是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一样