JavaScript DOM文档遍历实战

在介绍了《JavaScript DOM修改文档树方法实例》与《JavaScript DOM实战:创建和克隆元素》,本文将介绍JavaScript DOM文档遍历的详细知识,我们先从HTML页面最基本的组成元素讨论起:

<!--<html> 



    <head> 




        <title>DOM Examlie</title> 




    </head> 




    <body> 




       <p>Hello World !</p> 




</body> 




</html>-->  

访问<html>元素,你应该明白它是该文件的document元素,那你就可以使用document的documentElement属性:

var oHtml=document.documentElement;//可以直接访问<html>元素  


alert("节点名称 : "+oHtml.nodeName);//节点名称  


alert("节点类型 : "+oHtml.nodeType);//节点类型为 1  

获取<head> 和 <body>元素:

var oHead=oHtml.firstChild;//HEAD节点  



var oBody=oHtml.lastChild;//BODY节点  

也可以通过childNodes属性,获取<head> 和 <body>元素:

var oHead=oHtml.childNodes.item(0);//HEAD节点  



//var oHead=oHtml.childNodes[0];//简写,也有同样的结果是HEAD节点  




var oBody=oHtml.childNodes.item(1);//BODY节点  




//var oBody=oHtml.childNodes.item(1);//简写,也有同样的结果是BODY节点  

注意:方括号标记其实是NodeList在javascript中的简便实现。实际上正式的从childNodes列表中获取子节点的方法是使用item()方法:HTML DOM 中的专有属性 document.body ,它常用于直接访问元素:

var oBody=document.body;  

既然我们都知道了以上节点对象的获取方式,那我们用oHtml,oHead,oBody 这三个变量来确定一下它们之间的关系:

alert(oHead.parentNode==oHtml);//HEAD节点的父节点是BODY节点,返回 true   



alert(oBody.parentNode==oHtml);//BODY节点的父节点是BODY节点,返回 true   




alert(oBody.previousSibling==oHead);//BODY节点的上一个兄弟节点是HEAD节点 ,返回 true  




alert(oHead.nextSibling==oBody);//HEAD节点的下一个兄弟节点是BODY节点,返回 true  




alert(oHead.ownerDocument==document); //返回一个节点的根元素(Document),HEAD节点是否指向该文档,返回 true  

通过上面的学习我们已经了解遍历节点的最基本的方式, 也学会了如何找到某一个节点的兄弟节点及它的子节点。

复杂的节点遍历

在上面的学习中我们好像没有遇到过大的阻碍,下面我们以一个“导航条”为实例:

<div id="menu"> 



    <h1>我的导航条</h1>            




    <ul id="nav"> 




       <li><a href="#">HOME</a></li> 




       <li><a href="#">(X)Html / Css</a></li> 




       <li><a href="#">Ajax / RIA</a></li> 




       <li><a href="#">GoF</a></li> 




       <li><a href="#">JavaScript</a></li> 




       <li><a href="#">JavaWeb</a></li> 




       <li><a href="#">jQuery</a></li> 




       <li><a href="#">MooTools</a></li> 




       <li><a href="#">Python</a></li> 




       <li><a href="#">Resources</a></li> 




    </ul> 




</div>  

首先我想把看一下我的导航条下有多少个子节点。我第一想到的是前面我学过的查找元素的2种方法:

getElementById() # 通过ID属性查找元素

该方法将返回一个与那个有着给定id属性值的元素节点相对应的对象。

getElementsByTagName() # 通过标签名称查找元素

该方法返回一个对象数组,每个对象分别对应着文档里有着给定标签的一个元素。

<script type="text/javascript"> 


/*  


通过ID属性查找元素 ,用的是文档对象的getElementById()方法,  


查找到我们想要的元素对象,根据这个节点元素的 childNodes 属性,  


遍历出所有的子节点对象。  


*/  


function queryElementsId(){  


  var elemensArray,nav,nav_list;  



  elemensArray=[];  




  nav=document.getElementById("nav");  



  /*注意IE和FF中处理Text节点上存在着一些差异*/  



     navnav_list=nav.childNodes;  




     for(var i=0;i<nav_list.length;i++){  



    elemensArray[elemensArray.length]=nav_list[i];  


    //elemensArray.push(nav_list[i]); //同上一样的结果  


     }  


  return elemensArray;  


   


}  


/*  



我们观察到我的导航条是有规律的,是用无序列表元素组成的,只有定位到 &lt;ul&gt;元素  



;然后把getElementsByTagName()方法可以返回相同元素对象的集合,  


查用它找一组元素,太方便了。  


*/  


function queryElementsTagName(){  


 var elemensArray,nav,nav_list;  



 elemensArray=[];  




 var nav=document.getElementById("nav");  




 var navnav_list=nav.getElementsByTagName("li");//返回相同的一组元素  




 for(var i=0;i<nav_list.length;i++){  



  elemensArray[elemensArray.length]=nav_list[i];  


  //elemensArray.push(nav_list[i]); //同上一样的结果  


 }  


 return elemensArray;  


   


}  



</script>  

节点遍历

那我们接下来,测一下是否是我们想要的东西:

<script type="text/javascript"> 



window.onload=function(){  



     /*第一个方法*/  



     var list= queryElementsId();   



  /*第二个方法*/  



 //var list= queryElementsTagName();   




 var s="";  




 for(var i=0;i<list.length;i++){  



  s+=list[i].nodeName+"\n";   


    }  


 alert(s);  


 }  



</script>  

先看一下第一个方法queryElementsId()好像我们在IE中没有发现有什么问题,那我们在Firefox中看一下是否也是我们想要的结果。

这时,问题出现了,不同的浏览器在判断何为Text节点上存在着一些差异,例如在A级浏览器中的FF和IE就有很大的差异,FireFox会把元素之间的空白、换行、tab都是Text节点,IE下会把空白全部忽略掉,只有内联元素(如em,span)后的换行、空格、tab会被认为是一个Text。既然遇到了问题那我们就得解决问题,问题的根源我们也知道了,那相应的解决方案就好做了。

方法一:

<script type="text/javascript"> 


/*  


《精通javascript》上提供了一个函数,用于处理xm中的这些空格,其作用原理就是找出文本节点,并删除这些节点,以达到删除这些空格的目的。  


*/  


   


function cleanWhitespace(element){  


    //如果不提供参数,则处理整个HTML文档  



    elementelement = element || document;  



    //使用第一个子节点作为开始指针  



    var cur = element.firstChild;  



   


    //一直到没有子节点为止  


    while (cur != null){  


        //如果节点为文本节点,应且包含空格  



        if ( cur.nodeType == && ! /\S/.test(cur.nodeValue)){  



            //删除这个文本节点  


            element.removeChild( cur );  


   


            //否则,它就是一个元素  



        } else if (cur.nodeType == 1){  



            //递归整个文档  


            cleanWhitespace( cur );  


        }  


   



        curcur = cur.nextSibling;//遍历子节点  



    }  


}  



</script>  

方法二:

<script type="text/javascript"> 


   


/*  


最后,利用数组写了一个函数,能够有效的处理dom中的空格,其原理就是将一个元素的的父元素找出来,然后通过它父元素的childNodes属性找出该元素的所有兄弟元素。遍历该元素和它的兄弟元素,将所有元素节点放在一个数组里。这样调用这个数组,就只有元素节点而没有文本节点,也就没有了讨厌的空格.  


   


*/  


   


function cleanWhitespaces(elem){  


    //如果不提供参数,则处理整个HTML文档  



 var elemelem = elem || document;   




 var parentElem = elem.parentNode; //返回一个节点的父类节点  




 var childElem = parentElem.childNodes; //返回一个节点的子节点的节点列表  




 var childElemArray = new Array;   




 for (var i=0; i<childElem.length; i++){  




  if (childElem[i].nodeType==1){//把所有节点是元素节点类型的节点存放到数组里  



   childElemArray.push(childElem[i]);   


  }  


 }  


    return childElemArray;   


}   



</script>  

方法三:推荐

<script type="text/javascript"> 


/*  



原理是对元素的所有的子节点做一个遍历。然后做一个判断,如果是子元素节点(nodeType = 1),则遍历该子元素的所有的子节点,用递归检查是否包含空白节点;如果处理的子节点是文本节点(nodeType = 3),则检查是否是纯粹的空白节点,如果是,就将它从xml对象中删除。  



*/  


function removeWhitespace(xml){  


    var loopIndex;  


   



    for (loopIndex = 0; loopIndex < xml.childNodes.length; loopIndex++){  




        var currentNode = xml.childNodes[loopIndex];  




        if (currentNode.nodeType == 1){  



            removeWhitespace(currentNode);  


        }  


   



        if (((/^\s+$/.test(currentNode.nodeValue))) &&(currentNode.nodeType == 3)){  



            xml.removeChild(xml.childNodes[loopIndex--]);  


        }  


    }  


}  



</script>  

好了,我们在验证一下,#Text节点问题是否处理掉了。那我们就用方法3 中removeWhitespace(nav)方法来处理queryElementsId()方法中的#Text节点问题。

<script type="text/javascript"> 


function queryElementsId(){  


  var elemensArray,nav,nav_list;  



  elemensArray=[];  




  nav=document.getElementById("nav");  



  /*处理#Text节点问题*/  


  removeWhitespace(nav);  


   


  /*注意IE和FF中处理Text节点上存在着一些差异*/  



     navnav_list=nav.childNodes;  




     for(var i=0;i<nav_list.length;i++){  



    elemensArray[elemensArray.length]=nav_list[i];  


    //elemensArray.push(nav_list[i]); //同上一样的结果  


     }  


  return elemensArray;  


   


}  



</script>  

正如看到的结果,IE和FireFox中都没有问题了。

一个比较通用的方法:

<script type="text/javascript"> 


function text(elem){  



 var t="";  



 //如果传入的是元素,则继续遍历其子元素  


 //否则假定它是一个数组  



 elemelem=elem.childNodes || elem;  



 //遍历所有子节点  



 for(var i=0; i<elem.length;i++){  



     //如果不是元素,追加其文本值  


  //否则,递归遍历所有元素的子节点  


  t+=elem[i].nodeType !=1?elem[i].nodeValue:text(elem[i].childNodes);  


   


 }  


    //返回比配的文本  


    return t;  


}  



</script> 

用元素节点的DOM属性遍历DOM树

<script type="text/javascript"> 



window.onload=function(){  



  /*定位想要的节点*/  



  var nav=document.getElementById("nav");  



  /*查找父节点*/  



  var p_n=nav.parentNode;  



  alert("父节点的元素名称:"+p_n.nodeName);  


   


  /*处理FF遍历节点中的#Text */  


  removeWhitespace(nav);//移除所有的空Text节点  


   


  /*查找子节点*/    



  var c_n_f=nav.firstChild;//第一个节点对象  




  //var c_n_f=nav.childNodes[0];//同上一样的结果  




  var c_n_l=nav.lastChild;//最后一个节点对象  




  //var c_n_l=nav.childNodes[nav.childNodes.length-1];//同上一样的结果  



  alert("第一个节点:"+c_n_f.nodeName+"  "+"最后一个节点 :"+c_n_l.nodeName);   


  /*查找兄弟节点 或叫 相邻节点 */  


  /*用nextSibling和PreviousSibling必须有一个参考点,这样指针才知道自己往那里移动*/  



  var c_n_s=c_n_f.nextSibling;//第一个节点的下一个节点  



  alert("第一个节点的下一个节点:"+c_n_s.innerHTML+ "\n" + "节点中包含的HTML内容: "+c_n_s.nodeName);  


   


}  



</script> 

写到这里,既然标准的previousSibling,nextSibling,firstChild,lastChild,parentNode遍历方法有浏览器不兼容问题。我上面的解决方案是去掉遍历元素的相关空的#Text节点,是一个好的解决方案,但是使用起来不方便,我们何不自己写一些遍历节点的方法来代替标准的的previousSibling,nextSibling,firstChild,lastChild,parentNode。

我们的思路是利用元素是nodeType属性来判断元素是节点类型中那种节点类型,在DOM节点中我最常用的是元素节点,文本节点,属性节点,对应的类型值是元素节点nodeType=1 or ELEMENT_NODE, 文本节点 nodeType=2 or ATTRIBUTE_NODE,属性节点 nodeType=3 or TEXT_NODE,但是IE中并不支持命名常量,那就用数值吧,再配合标准的遍历属性。完全可以自己生产一些辅助函数来取代标准的遍历方式。以下一系列的辅助函数可以帮助您,他们能取代标准的previousSibling,nextSibling,firstChild,lastChild,parentNode;

相关推荐