1. 程式人生 > >深入理解javascript選擇器API系列第二篇——getElementsByClassName

深入理解javascript選擇器API系列第二篇——getElementsByClassName

前面的話

  既然有getElementById()getElementsByTagName()方法,為什麼沒有getElementsByClassName()呢?id屬性、標籤名、class屬性並沒有什麼優劣之分啊。終於,HTML5新增了getElementsByClassName()方法,由於在CSS佈局中類名的廣泛使用,該方法正好切中痛點,使得通過類名選取元素不再困難,成為最受歡迎的一個方法。接下來,本文將詳細介紹該方法

使用

  HTML元素的class屬性值是一個以空格隔開的列表,可以為空或包含多個識別符號。在javascript中class是保留字,所以使用className屬性來儲存HTML的class屬性值

  getElementsByClassName()方法接收一個引數,即一個包含一個或多個類名的字串,返回帶有指定類的所有元素的類陣列物件HTMLCollection。傳入多個類名時,類名的先後順序不重要。與getElementsByTagName()類似,該方法既可以用於HTML文件物件,也可以用於element元素物件

  [注意]IE8-瀏覽器不支援

<ul id="list">
    <li class="a ab c">1</li>
    <li class="a">2</li>
    <li class="ac">3</li>
    <li class="a b c">4</li>
    <li class="a b">5</li>
</ul>
<script>
//
類名中存在a成立 Array.prototype.forEach.call(list.getElementsByClassName('a'),function(item,index,arr){ item.style.fontWeight = 'bold'; }); //只有類名中同時存在a和c才成立 Array.prototype.forEach.call(list.getElementsByClassName('a c'),function(item,index,arr){ item.style.color = 'red'; }); </script>

classList屬性

  在操作類名時,需要通過className屬性新增、刪除和替換類名。因為className是一個字串,所以即使只修改字串一部分,也必須每次都設定整個字串的值。要從className字串中刪除一個類名,需要把類名拆開,刪除不想要的那個,再重新拼成一個新字串

  HTML5為所有元素添加了classList屬性,這個classList屬性是新集合型別DOMTokenList的例項,它有一個表示自己包含多少元素的length屬性,而要取得每個元素可以使用item()方法,也可以使用方括號法

  [注意]IE9-瀏覽器不支援

<div id="test" class="a b c"></div>
<script>
console.log(test.classList);//["a", "b", "c", value: "a b c"]
console.log(test.classList[0]);//a
console.log(test.classList.item(1));//b
</script>

  此外,這個新型別還定義如下方法:

add(value)             將給定的字串值新增到列表中,如果值已存在,則不新增
contains(value)        表示列表中是否存在給定的值,如果存在則返回true,否則返回false
remove(value)          從列表中刪除給定的字串
toggle(value)          如果列表中已經存在給定的值,刪除它;如果列表中沒有給定的值,新增它

  有了classList屬性,className屬性基本沒有什麼用武之地了

<style>
.cB{color: blue;}
</style>

<body>
<div id="test">測試文字</div>
<button id="btn1" onclick = "test.classList.add('cB')">add</button>
<button id="btn2" onclick = "test.classList.contains('cB')?alert(true):alert(false)">contains</button>
<button id="btn3" onclick = "test.classList.remove('cB')">remove</button>
<button id="btn4" onclick = "test.classList.toggle('cB')">toggle</button>
</body>

擴充套件

  【1】由於原生的getElementsByClassName()方法不相容IE8-瀏覽器,且該方法只能完全匹配引數中的類名列表。因此有如下擴充套件

  擴充套件函式getElementsByClassName(),具有更豐富的功能。如果引數類名列表由空格分隔,則進行且匹配,即只有元素中的類名包含引數類名列表中的所有類名才算匹配成功;如果引數類名列表由逗號分隔,則進行或匹配,即只要元素中的類名包含引數類名列表中的其中一個型別就算匹配成功

Array.prototype.noRepeat = function(){
    var result = [];
    for(var i = 0; i < this.length; i++){
        if(result.indexOf(this[i]) == -1){
            result.push(this[i]);
        }
    }
    return result;
}
Array.prototype.inArray = function(value){
    for(var i = 0; i < this.length; i++){
        if(this[i] === value){
            return true;
        }
    }
    return false;
}
function getElementsByClassName(parentObj,classStr){
    var result = [];
    //獲取parentObj下的所有子元素
    var objs = parentObj.getElementsByTagName('*');
    //條件一:如果classStr用空格分隔,則表示class必須同時滿足才有效
    var targetArr1 = classStr.trim().split(/\s+/).noRepeat();
    //條件二:如果classStr用逗號分隔,則表示class只要有一個滿足就有效
    var targetArr2 = classStr.trim().split(/\s*,\s*/).noRepeat();
    //只有一個class或者進行條件一測試
    if(classStr.indexOf(',') == -1 ){
        label: for(var i = 0; i < objs.length; i++){
            //獲取每一個子元素的類名,將其轉換為陣列後去重
            var arr = objs[i].className.trim().split(/\s+/).noRepeat();
            //進入迴圈,測試是否符合條件一
            for(var j = 0; j < targetArr1.length; j++){
                //如果條件一中的某一項在arr陣列中不存在,則跳過該子元素
                if(!arr.inArray(targetArr1[j])){
                    continue label;
                }
            }
            //將符合條件一的子元素物件放在結果陣列中
            result.push(objs[i]);
        }
        //返回結果陣列
        return result;
    //進行條件二測試
    }else{
        label: for(var i = 0; i < objs.length; i++){
                //獲取每一個子元素的類名,將其轉換為陣列後去重
                var arr =objs[i].className.trim().split(/\s+/).noRepeat();
                //進入迴圈,測試是否符合條件二
                for(var j = 0; j < targetArr2.length; j++){
                    //只要條件二的中某一項在arr陣列中存在,就符合
                    if(arr.inArray(targetArr2[j])){
                        //將符合條件二的子元素物件放在結果陣列中
                        result.push(objs[i]);
                        //接著進入下一個子元素測試
                        continue label;
                    }
                }   
            }
        //返回結果陣列
        return result;     
    }
}
<ul id="list">
    <li class="a ab c">1</li>
    <li class="a">2</li>
    <li class="ac">3</li>
    <li class="a b c">4</li>
    <li class="a b">5</li>
</ul>
<script>
//類名中存在a成立
getElementsByClassName(list,'a').forEach(function(item,index,arr){
    item.style.fontWeight = 'bold';
});
//只有類名中同時存在a和c才成立
getElementsByClassName(list,'a c').forEach(function(item,index,arr){
    item.style.color = 'red';
});
//只要類名中存在b或c即成立
getElementsByClassName(list,'b,c').forEach(function(item,index,arr){
    item.style.backgroundColor = 'pink';
});
</script>

  【2】由於IE9-瀏覽器不支援classList屬性,也就不支援add()、contains()、remove()和toggle()這四個方法,下面是這四個方法的相容寫法

  由於indexOf()和trim()方法都是ES5新增方法,在低版本IE瀏覽器中不支援,所以需要重新封裝

//陣列的indexOf方法封裝
function indexOf(arr,value,start){
    //如果不設定start,則預設start為0
    if(arguments.length == 2){
        start = 0;
    }
    //如果陣列中存在indexOf方法,則用原生的indexOf方法
    if(arr.indexOf){
        return arr.indexOf(value,start);
    }
    for(var i = start; i < arr.length; i++){
        if(arr[i] === value){
            return i;
        }
    }
    return -1;
}
//陣列去重方法封裝
function noRepeat(arr){
    var result = [];
    for( var i = 0; i < arr.length; i++){
        if(indexOf(result,arr[i]) == -1){
            result.push(arr[i]);
        }
    }
    return result;
}
//inArray方法封裝
function inArray(arr,value){
    for(var i = 0; i < arr.length; i++){
        if(arr[i] === value){
            return true;
        }
    }
    return false;
}
//去除首尾空格函式封裝
function trim(arr){
    var result = arr.replace(/^\s+|\s+$/g,'');
    return result;
}

  1、add函式封裝

function addClass(obj,classStr){
    var array = noRepeat(trim(obj.className).split('\s+'));
    if(!inArray(array,classStr)){
        array.push(classStr);
    }
    obj.className = array.join(' ');
    return obj;
}

  2、contains函式封裝

function containsClass(obj,classStr){
    var array = noRepeat(trim(obj.className).split('\s+'));
    if(inArray(array,classStr)){
        return true;
    }
    return false;
}

  3、remove函式封裝

function removeClass(obj,classStr){
    var array = noRepeat(trim(obj.className).split('\s+'));
    var index = indexOf(array,classStr);
    if(index != -1){
        array.splice(index,1);
        obj.className = array.join(' ');
    }
    return obj;
}

  4、toggle函式封裝

function toggleClass(obj,classStr){
    var array = noRepeat(trim(obj.className).split('\s+'));
    if(inArray(array,classStr)){
        removeClass(obj,classStr);
    }else{
        addClass(obj,classStr);
    }
}
<style>
.cB{color: blue;}
</style>

<div id="test">測試文字</div>
<button id="btn1" onclick = "addClass(test,'cB')">add</button>
<button id="btn2" onclick = "containsClass(test,'cB')?alert(true):alert(false)">contains</button>
<button id="btn3" onclick = "removeClass(test,'cB')">remove</button>
<button id="btn4" onclick = "toggleClass(test,'cB')">toggle</button>