這篇文章主要介紹了JavaScript跨浏覽器獲取頁面中相同class節點的方法,本文講解使用getELementsByClassName函數解決這個需求,並給了一個開源的getELementsByClassName函數實現,功能更加強大,需要的朋友可以參考下
網頁開發時,在很多時候我們需要操作相同類名的元素,即class相同的元素。昨天參加筆試,有一道相關的題目沒答上來:
JavaScript獲取頁面中class為test的節點
於是收集了一些相關的資料,在本文中列舉了兩種我覺得比較好的方法,不足之處,還望大家批評指正。如果大家有更好的方法,希望可以分享。
Solution1 Jeremy Keuth方案
Jeremy Keuth大叔在《JavaScript DOM 編程藝術》(第2版)(英文:DOM Scripting-Web Design with JavaScript and the Document Object Model)一書的第三章第四節中講到了getElementsByClass這個方法,並講到了如何在不支持該屬性的浏覽器(IE6,IE7和IE8,讓我們鄙視他們)中應用這一方法,摘錄在此,個別地方有修改。
HTML5 DOM中新增了一個方法讓我們通過class屬性中的類名來訪問元素,這就是:getELementsByClassName,由於方法比較新,某些的DOM實現裡還沒有,因此在使用的時候要當心。下面我們先來看一看這個方法能幫我們做什麼,然後在討論怎麼可靠的使用該方法。
與getELementsByTagName方法類似,getElementsByClassName也只接受一個參數,就是類名:
代碼如下:
getElementsByClassName(class)
這個方法的返回值也與getElementsByTagName類似,都是一個具有相同類名的元素的數組,下面這行代碼返回的就是一個數組,其中包含類名為“sale”的所有元素:
代碼如下:
document.getElementsByClassName("sale")
使用這個方法還可以查找那些帶有多個類名的元素。要指定多個類名,只要在字符串參數中用空格分隔類名即可。例如,在<script>標簽中添加下面這行代碼:
代碼如下:
alert(document.getElementsByClassName("sale important").length);
完整代碼
代碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Shopping List</title>
</head>
<body>
<h1>What to buy</h1>
<p title="a gentle reminder">Don't forget to buy this stuff.</p>
<ul id="purchase">
<li>A thin of beans</li>
<li class="sale">Cheese</li>
<li class="sale important">Milk</li>
</ul>
<script>
alert(document.getElementsByClassName("sale important").length);
</script>
</body>
</html>
你會看到警告框中顯示1,表示只有一個元素匹配,因為只有一個元素同時帶有”important”和”sale”類名。注意,即使在元素的class屬性中,類名的順序是”sale important”而非參數中指定的”important sale”,也會照樣匹配該元素。不僅類名的實際順序不重要,就算元素還帶有更多類名也沒有關系。與使用getELementsByTagName一樣,也可以組合使用getElementsByClassName和getElementById。如果你想知道在id為purchase的元素中有多少類名包含test的列表項,可以先找到那個特定的對象,然後再調用getElementsByClassName:
代碼如下:
var shopping=document.getElementById("purchase");
var sales = shopping.getElementsByClassName("sale");
這樣,sales數組中包含的就只是位於”purchase”列表中的帶有”sales”類的元素,運行下面這行代碼,就會看到sales數組包含兩項:
代碼如下:
alert(sales.length);
這個getELementsByClassName方法非常有用,但只有較新的浏覽器(Safari 3.1,Chorme,Firefox 3 and Opera 9.5以上)才支持它。為了彌補這一不足,DOM腳本程序員需要使用已有的DOM方法來實現自己的getElementsByClassName,有點像成人禮似的。而多數情況下,他們的實現過程都與下面這個getElementsByClassName大致相似,這個函數能適用於新老浏覽器。
代碼如下:
function getElementsByClassName(node,classname){
if(node.getElementsByClassName){
return node.getElementsByClassName(classname);
}else{
var results = [];
var elems = node.getElementsByTagName("*");
for(var i=0;i<elems.length;i++){
if(elems[i].className.indexOf(classname)!=-1){
results[results.length]=elems[i];
}
}
return results;
}
}
這個getElementsByClassName函數接受兩個參數。第一個node表示DOM樹中的搜索起點,第二個classname就是要搜索的類名了。如果傳入節點上已經存在了適當的getElementsByClassName函數,那麼這個新函數就直接返回相應的節點列表。如果getElementsByClassName函數不存在,這個新函數就會循環遍歷所有標簽,查找帶有相應類名的元素。
這個方法的缺點是不適用於多個類名。
如果使用這個函數來模擬前面取得購物列表的操作,就可以這樣寫:
代碼如下:
var shopping=document.getElementById("purchase");
var sales = shopping.getElementsByClassName(shopping,"test");
console.log(sales);
因此,要解決文章開頭的那道題目,所用代碼如下:
代碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Shopping List</title>
</head>
<body>
<h1>What to buy</h1>
<p title="a gentle reminder">Don't forget to buy this stuff.</p>
<ul id="purchase">
<li>A thin of beans</li>
<li class="sale">Cheese</li>
<li class="sale important">Milk</li>
</ul>
<script>
function getElementsByClassName(node,classname){
if(node.getElementsByClassName){
return node.getElementsByClassName(classname);
}else{
var results = [];
var elems = node.getElementsByTagName("*");
for(var i=0;i<elems.length;i++){
if(elems[i].className.indexOf(classname)!=-1){
results[results.length]=elems[i];
}
}
return results;
}
}
var body = document.getElementsByTagName("body")[0];
var sales= getElementsByClassName(body,"sales");
console.log(sales);
</script>
</body>
</html>
Solution2 Robert Nyman方案
搜索匹配的DOM元素的方法還有很多,但真正高效的卻不多,Jeremy Keuth大叔的方法有一個缺點就是不能用於多個類名,2008年,Robert Nyman在文章 The Ultimate GetElementsByClassName, Anno 2008中提供了自己的解決方案。在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大叔的方法,有些地方還不太明白,待我研究一下再來更新好了。
代碼如下:
/*
Developed by Robert Nyman, http://www.robertnyman.com
Code/licensing: http://code.google.com/p/getelementsbyclassname/
*/
var getElementsByClassName = function (className, tag, elm){
if (document.getElementsByClassName) {
getElementsByClassName = function (className, tag, elm) {
elm = elm || document;
var elements = elm.getElementsByClassName(className),
nodeName = (tag)? new RegExp("b" + tag + "b", "i") : null,
returnElements = [],
current;
for(var i=0, il=elements.length; i<il; i+=1){
current = elements[i];
if(!nodeName || nodeName.test(current.nodeName)) {
returnElements.push(current);
}
}
return returnElements;
};
}
else if (document.evaluate) {
getElementsByClassName = function (className, tag, elm) {
tag = tag || "*";
elm = elm || document;
var classes = className.split(" "),
classesToCheck = "",
xhtmlNamespace = "http://www.w3.org/1999/xhtml",
namespaceResolver = (document.documentElement.namespaceURI === xhtmlNamespace)? xhtmlNamespace : null,
returnElements = [],
elements,
node;
for(var j=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);
}
return returnElements;
};
}
else {
getElementsByClassName = function (className, tag, elm) {
tag = tag || "*";
elm = elm || document;
var classes = className.split(" "),
classesToCheck = [],
elements = (tag === "*" && elm.all)? elm.all : elm.getElementsByTagName(tag),
current,
returnElements = [],
match;
for(var k=0, kl=classes.length; k<kl; k+=1){
classesToCheck.push(new RegExp("(^|s)" + classes[k] + "(s|$)"));
}
for(var l=0, ll=elements.length; l<ll; l+=1){
current = elements[l];
match = false;
for(var m=0, ml=classesToCheck.length; m<ml; m+=1){
match = classesToCheck[m].test(current.className);
if (!match) {
break;
}
}
if (match) {
returnElements.push(current);
}
}
return returnElements;
};
}
return getElementsByClassName(className, tag, elm);
};