classList - 惰性载入 - 利用返回值

classList就不做介绍了,HTML 5的一个操作类名的API。
四个方法:

  • add(str)
  • remove(str)
  • contains(str)
  • toggle(str)

需要注意的是,作为参数传入的必须是一个className,而不能是两个或者多个。并且对传入的字符串大小写敏感。在做remove操作时,同名的className也会删除。浏览器支持情况

这里自己封装removeClass和toggleClass的函数,contains就不说了。add和remove类似,也不说了。这里remove用到一个惰性载入,如果浏览器支持,指定相应的函数,无需在下一次执行还需要判断。只是在第一次调用时会损失一点点性能。

function removeClass(element,str){
    if (typeof element.classList !== 'undefined'){
        removeClass = function (){
            element.classList.remove(str);
        };
    } else {
        removeClass = function (){
            var classNames = element.className.split(/\s+/),
                i = classNames.length;
            for (; i--; ){
                if(classNames[i] === str){
                    classNames.splice(i,1);
                /*
                    element.className = classNames.join(" ");
                    return;    //在此直接return真的合适吗?如果有同名的两个className呢?
                    //这个classList API 可是会将同名的也删除呢。尽管这可能是其他方面的问题。
                    //比如HTML里不小心放了两个同名className,或者JS中意外添加了同名className,
                    //不过,因此,也要考虑这方面情况。
                */
                }
            }
            element.className = classNames.join(" ");
        };
    }
    return removeClass(element,str);
}

下面是toggleClass函数,这里用到另一种形式的惰性载入,直接运行一个匿名函数,在函数声明时就指定相应的函数。这种方式,只在代码首次加载时会损失一点性能。

var toggleClass =  (function (){
    if (typeof document.body.classList !== 'undefined'){
        return function (element,str){
            element.classList.toggle(str);
        };
    } else {
        return function (element,str){
            var classNames = element.className.split(/\s+/),
                i = (len = classNames.length);
            for (; i--; ){
                if(classNames[i] === str){
                    classNames.splice(i,1);
                }
            }
            if(len === classNames.length){
                classNames.push(str);
            }
            element.className = classNames.join(" ");    
        };
    }
})();

有没发现,上面部分的代码,很大部分是一样。
这个API作为一个模块,支持了remove,也就支持了toggle
对于不支持classList的浏览器,分析下toggle的逻辑:如果原来的没有匹配的className,添加;如果有匹配,删除。而remove的逻辑呢:没有匹配到,什么都不做;有匹配到,删除。其都要循环遍历所有的className组成的数组。

那么,我给removeClass内部,如果匹配到传入的className,做了删除操作后,返回一个true代表已经做了删除操作怎么样。这样,就“告诉”了函数,不要做add操作了。而如果其没有匹配到传入的className,原来是什么都不做,可以给它返回一个false或者 还是什么都不做(默认返回undefined)。既然没有做remove操作,那么就是做add了。

下面请看代码:

var removeClass = null,
    toggleClass = null;
if(typeof document.body.classList !== 'undefined'){
    removeClass = function (element,str){
        element.classList.remove(str);
    };
    toggleClass = function (element,str){
        element.classList.toggle(str);
    }
} else {
    removeClass = function (elements,str){
        var classNames = element.className.split(/\s+/),
            i = (len = classNames.length);
        for (; i--; ){
            if(classNames[i] === str){
                classNames.splice(i,1);
            }
        }
        if(len !== classNames.length){
            element.className = classNames.join(" ");
            return true;
        }
    };
    toggleClass = function (elements,str){
        if(!removeClass(element,str)){
            element.className += (' ' + str);
        }
    };
}

当然,性能降低了一点点,涉及到了字符串拼接。不过倒是代码简短了。此外,用的是递减的迭代,所以,也有一点点好处,毕竟添加操作,都是在末尾进行的,无论是前面用的push然后concat,还是这里的字符串拼接。也就是说,其实有的时候for循环只需要执行一步就够了。

此外,可以再扩展下,实现多个className的删除和切换。

function toggleClass2(element,str){
    var classNames = element.className.split(/\s+/),
        arr = str.split(/\s+/),
        i,
        j,
        len = classNames.length;
    for(j = arr.length; j-- ; ){
        for (i = classNames.length; i-- ; ){
            if(classNames[i] === arr[j]){
                classNames.splice(i,1);
            }
        }
    }
    if(len === classNames.length){
        for(j = arr.length; j-- ; ){
            classNames.push(arr[j]);
        }
    }
    element.className = classNames.join(" ");
}

其实也没必要了,原本一个元素的className也就那几个。