JavaScript跨浏览器获取页面中相同class节点的方法
网页开发时,在很多时候我们需要操作相同类名的元素,即class相同的元素。昨天参加笔试,有一道相关的题目没答上来:
JavaScript获取页面中class为test的节点
于是收集了一些相关的资料,在本文中列举了两种我觉得比较好的方法,不足之处,还望大家批评指正。如果大家有更好的方法,希望可以分享。
Solution1JeremyKeuth方案
JeremyKeuth大叔在《JavaScriptDOM编程艺术》(第2版)(英文:DOMScripting-WebDesignwithJavaScriptandtheDocumentObjectModel)一书的第三章第四节中讲到了getElementsByClass这个方法,并讲到了如何在不支持该属性的浏览器(IE6,IE7和IE8,让我们鄙视他们)中应用这一方法,摘录在此,个别地方有修改。
HTML5DOM中新增了一个方法让我们通过class属性中的类名来访问元素,这就是:getELementsByClassName,由于方法比较新,某些的DOM实现里还没有,因此在使用的时候要当心。下面我们先来看一看这个方法能帮我们做什么,然后在讨论怎么可靠的使用该方法。
与getELementsByTagName方法类似,getElementsByClassName也只接受一个参数,就是类名:
getElementsByClassName(class)
这个方法的返回值也与getElementsByTagName类似,都是一个具有相同类名的元素的数组,下面这行代码返回的就是一个数组,其中包含类名为“sale”的所有元素:
document.getElementsByClassName("sale")
使用这个方法还可以查找那些带有多个类名的元素。要指定多个类名,只要在字符串参数中用空格分隔类名即可。例如,在<script>标签中添加下面这行代码:
alert(document.getElementsByClassName("saleimportant").length);
完整代码
<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8">
<title>ShoppingList</title>
</head>
<body>
<h1>Whattobuy</h1>
<ptitle="agentlereminder">Don'tforgettobuythisstuff.</p>
<ulid="purchase">
<li>Athinofbeans</li>
<liclass="sale">Cheese</li>
<liclass="saleimportant">Milk</li>
</ul>
<script>
alert(document.getElementsByClassName("saleimportant").length);
</script>
</body>
</html>
你会看到警告框中显示1,表示只有一个元素匹配,因为只有一个元素同时带有”important”和”sale”类名。注意,即使在元素的class属性中,类名的顺序是”saleimportant”而非参数中指定的”importantsale”,也会照样匹配该元素。不仅类名的实际顺序不重要,就算元素还带有更多类名也没有关系。与使用getELementsByTagName一样,也可以组合使用getElementsByClassName和getElementById。如果你想知道在id为purchase的元素中有多少类名包含test的列表项,可以先找到那个特定的对象,然后再调用getElementsByClassName:
varshopping=document.getElementById("purchase");
varsales=shopping.getElementsByClassName("sale");
这样,sales数组中包含的就只是位于”purchase”列表中的带有”sales”类的元素,运行下面这行代码,就会看到sales数组包含两项:
alert(sales.length);
这个getELementsByClassName方法非常有用,但只有较新的浏览器(Safari3.1,Chorme,Firefox3andOpera9.5以上)才支持它。为了弥补这一不足,DOM脚本程序员需要使用已有的DOM方法来实现自己的getElementsByClassName,有点像成人礼似的。而多数情况下,他们的实现过程都与下面这个getElementsByClassName大致相似,这个函数能适用于新老浏览器。
functiongetElementsByClassName(node,classname){
if(node.getElementsByClassName){
returnnode.getElementsByClassName(classname);
}else{
varresults=[];
varelems=node.getElementsByTagName("*");
for(vari=0;i<elems.length;i++){
if(elems[i].className.indexOf(classname)!=-1){
results[results.length]=elems[i];
}
}
returnresults;
}
}
这个getElementsByClassName函数接受两个参数。第一个node表示DOM树中的搜索起点,第二个classname就是要搜索的类名了。如果传入节点上已经存在了适当的getElementsByClassName函数,那么这个新函数就直接返回相应的节点列表。如果getElementsByClassName函数不存在,这个新函数就会循环遍历所有标签,查找带有相应类名的元素。
这个方法的缺点是不适用于多个类名。
如果使用这个函数来模拟前面取得购物列表的操作,就可以这样写:
varshopping=document.getElementById("purchase");
varsales=shopping.getElementsByClassName(shopping,"test");
console.log(sales);
因此,要解决文章开头的那道题目,所用代码如下:
<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8">
<title>ShoppingList</title>
</head>
<body>
<h1>Whattobuy</h1>
<ptitle="agentlereminder">Don'tforgettobuythisstuff.</p>
<ulid="purchase">
<li>Athinofbeans</li>
<liclass="sale">Cheese</li>
<liclass="saleimportant">Milk</li>
</ul>
<script>
functiongetElementsByClassName(node,classname){
if(node.getElementsByClassName){
returnnode.getElementsByClassName(classname);
}else{
varresults=[];
varelems=node.getElementsByTagName("*");
for(vari=0;i<elems.length;i++){
if(elems[i].className.indexOf(classname)!=-1){
results[results.length]=elems[i];
}
}
returnresults;
}
}
varbody=document.getElementsByTagName("body")[0];
varsales=getElementsByClassName(body,"sales");
console.log(sales);
</script>
</body>
</html>
Solution2RobertNyman方案
搜索匹配的DOM元素的方法还有很多,但真正高效的却不多,JeremyKeuth大叔的方法有一个缺点就是不能用于多个类名,2008年,RobertNyman在文章TheUltimateGetElementsByClassName,Anno2008中提供了自己的解决方案。在2005年,Robert大叔就已经给出了自己的getElementsByClassName的函数,在2008年的时候,修改了部分代码,添加了许多新的功能:
1.如果当前浏览器支持getElementsByClassName函数,则调用该原生函数;
2.如果当前浏览器支持则使用XPath;//小飞鱼:一种浏览器内置的定位XML文档的强大方式,不过浏览器支持方面不统一
3.支持多个类名的搜索,不计先后顺序;
4.返回真正的节点数组,而不是原生的一个nodelist。//小飞鱼:原生的getElementsByClassName方法返回的是一个NodeList对象,它很像数组,有length和数字索引属性,但并不是数组,不能用pop,push等数组特有的方法,Robert提供的代码中,将NodeList对象转成了数组。可以将NodeList对象转换成数组的方法:
myList=Array.prototype.slice.call(myNodeList)
这是Robert大叔的方法,有些地方还不太明白,待我研究一下再来更新好了。
/*
DevelopedbyRobertNyman,http://www.robertnyman.com
Code/licensing:http://code.google.com/p/getelementsbyclassname/
*/
vargetElementsByClassName=function(className,tag,elm){
if(document.getElementsByClassName){
getElementsByClassName=function(className,tag,elm){
elm=elm||document;
varelements=elm.getElementsByClassName(className),
nodeName=(tag)?newRegExp("\\b"+tag+"\\b","i"):null,
returnElements=[],
current;
for(vari=0,il=elements.length;i<il;i+=1){
current=elements[i];
if(!nodeName||nodeName.test(current.nodeName)){
returnElements.push(current);
}
}
returnreturnElements;
};
}
elseif(document.evaluate){
getElementsByClassName=function(className,tag,elm){
tag=tag||"*";
elm=elm||document;
varclasses=className.split(""),
classesToCheck="",
xhtmlNamespace="http://www.w3.org/1999/xhtml",
namespaceResolver=(document.documentElement.namespaceURI===xhtmlNamespace)?xhtmlNamespace:null,
returnElements=[],
elements,
node;
for(varj=0,jl=classes.length;j<jl;j+=1){
classesToCheck+="[contains(concat('',@class,''),'"+classes[j]+"')]";
}
try{
elements=document.evaluate(".//"+tag+classesToCheck,elm,namespaceResolver,0,null);
}
catch(e){
elements=document.evaluate(".//"+tag+classesToCheck,elm,null,0,null);
}
while((node=elements.iterateNext())){
returnElements.push(node);
}
returnreturnElements;
};
}
else{
getElementsByClassName=function(className,tag,elm){
tag=tag||"*";
elm=elm||document;
varclasses=className.split(""),
classesToCheck=[],
elements=(tag==="*"&&elm.all)?elm.all:elm.getElementsByTagName(tag),
current,
returnElements=[],
match;
for(vark=0,kl=classes.length;k<kl;k+=1){
classesToCheck.push(newRegExp("(^|\\s)"+classes[k]+"(\\s|$)"));
}
for(varl=0,ll=elements.length;l<ll;l+=1){
current=elements[l];
match=false;
for(varm=0,ml=classesToCheck.length;m<ml;m+=1){
match=classesToCheck[m].test(current.className);
if(!match){
break;
}
}
if(match){
returnElements.push(current);
}
}
returnreturnElements;
};
}
returngetElementsByClassName(className,tag,elm);
};