关于className的一点新的认识

用来判断一个字符串是否包含另一个字符串的方法,之前一直觉得,如果可以使用正则的地方,使用正则是最简单的,当时这么想,也只能说我对正则的理解还不到位吧,或者是说,我对原生JS的API的理解还不够透彻吧。比如我之前差不多把所有的查找相关的信息,都是使用正则实现的。

写在前面

在之前有过一段时间,一直以为如果可以使用代码直接操作数组时效率最高的,为此也去研究各种排序算法,因为当时觉得排序是比较原生的效果更好吧,所以就二了一把~~后来随着自己对JS的认识提高才发现,原生的排序比所谓的排序算法的效率高出了何止一个档次,简直就是天壤之别,请参考:巧用原生API生成多个不相等的随机数强大的replace方法数组去重方法在不同浏览器下效率,这都是算之前写过的相关的文章了,也是记录了我对JS认识的一点过程。

当然也记录了我实现一些功能,喜欢正则的原因,因为正则可以以很简单的代码,完成一段比较不错的功能啊。比如上周的关于HTML框架的一个简单的代码,也是使用正则的,请参考:JS实现简单的template。OK,废话就到这里,下面继续本篇的一点小认识。

元素的class的操作

话说,对于HTML元素的样式更改,我们都是使用的添加或者删除某个class实现的,(对于那种计算定位,移动数值的,就不多说了),这里最简单的也就是直接获取和定义class了,首先一点就是关于class的获取与赋值:

简单示例:


/HTML/
<div id = "test" class = "">这是用来测试的</div>



//CSS
div{
	padding:5px;
	border:1px solid #ddd;
	background-color:#fff;
}
.newClass{
	color:#333;
	padding:5px;
	border:1px solid #aaa;
	background-color:#ddd;
}



//JS
var test = document.getElementById("test");
test.onmouseover = function(){
    test.className = "newClass";
}

test.onmouseout = function(){
    test.className = "";
}


这里,我也只是使用了最简单最常用的方法,进行了className的更改,说到这里,当然还会有其他的获取class的方法了,比如getAttribute获取属性中的class。只是这两种方法,在某些时候,是有一些区别的,这里不做说明,有兴趣的可以查看:js原生操作HTML对象的属性区别jquery中attr方法和prop方法的区别

判断class是否存在

组合使用一些class,做到CSS样式的重复利用,在我们来说,是很正常的,可是这个时候,再去修改一个HTML元素的class时,就不能像上面的那么简单了,首先,我们必须判断当前需要操作的class是否存在,因为一味的添加同一名称的class的话,让人看起来简直不可忍受。而且也会导致HTML部分,一个元素的class显得太二(反正就是看起来很不爽就对了)。

所以在我们操作一个元素的class之前,首先就要先判断需要操作的class是否存在于目标元素上。

说道这里,就不得不说一个自己一个很二,但是到今天才意识到的问题,那就是使用正则表达式进行判断。还是看代码吧:


var classList = document.getElementById("test").className;
function hasClass(classList,className){
    var reg = new RegExp("(?:^|\s+)"+className+"(?:\s+|$)","g");
	
    return reg.test(classList);
}

console.log(hasClass(classList,"newClass"));//true


就是上面的样式,是我曾经以为很不错的,感觉使用的正则表达式在处理这个问题,看起来很高档的样子。。。就算之前在看jQuery源码时,也没有太仔细看关于获取class的相关的内容,因为感觉自己已经理解到了这块的操作思想,所以关于jQuery中的addClass,removeClass,hasClass都是直接跳过的。而这个误区,却是一直延续到现在,才再次意识到。

今天是看到了一本书中提到了mootools中,的contains方法的实现:


function contains(target,str,separator){
    return separator?
        (separator+target+separator).indexOf(separator+str+separator) > -1:
        target.indexOf(str)>-1;
}


使用该方法,我们可以更简单的完成hasClass方法的功能模块,通过contains方法的功能,我们可以直接重写hasClass的方法如下:


function hasClass(classList,className){
    return contains(classList,className," ");
}


就这样就可以完成了,不需要使用正则表达式,虽然正则表达式看起来很有水平的样子,只是效率却是低了,所以,当我看到这段代码时,突然就有了一种眼前一亮,原来如此。

同时,也想到一个问题,难道mootools使用的这么优秀的方法,而jQuery就用的一个很差的方法吗,所以也就再次翻了一下jQuery的源码,才发现~~~我究竟是错过了什么。

下面是jQueryhasClass的源码部分:


rclass = /[\t\r\n\f]/g;
//匹配\t(水平制表符),\r(回车),\n(换行),\f(换页)
//之所以使用该正则表达式,应该是为了有些页面,使用了防止xss攻击的写法
//关于xss攻击的知识,不太了解,所以~~
hasClass: function( selector ) {
    var className = " " + selector + " ",
	i = 0,
	l = this.length;
	
    for ( ; i < l; i++ ) {
	if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
	    return true;
	}
    }

    return false;
}


看到上面的代码,也应该算是能看出来了吧,核心代码为


if ( this[i].nodeType === 1 && 
    (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {

    return true;
}


首先先判断,目标元素是否为Element元素,如果是Element元素,则获取元素的className并在获取的值的开头和结尾添加空格,再使用正则把值当中的回车、制表符、换页、换行换成空格符,继而使用indexOf判断className是否存在。但是这里要注意一点,jQuery的选择器获取的值是以数组的形式存在的,所以看这里,使用hasClass的目标元素,也是一个jQuery数组,而hasClass方法,却是对这个数组的所有元素进行遍历,只要有一个元素中,含有需要查找的className,那么该方法就会返回true

所以,看下面这个例子:


<div class = "test newClass">这是用来测试的</div>
<div class = "test">这是用来测试的</div>
console.log($(".test").hasClass("newClass"));


这个时候,是返回为true的,只有当上面两个元素都不包含newClass时,才会返回false,这里在我看来,是有些不合理的,所以对此也不做评论。只是注意一下即可。

其他

说到这里,就顺便也带入一些HTML5的东西吧,比如class操作中,包含的一个多么令人高兴的classList属性,它直接可以进行class的增减,切换,查看有无等各种class相关的操作,除非是给对应的元素赋空值,或者重写整个className,那么classList无疑是最优的方法了。

继续做一个简单的例子:


<div id = "test" class = "test newClass">这是用来测试的</div>



var test = document.getElementById("test").classList;
console.log(typeof test); //object
console.log(Object.prototype.toString.call(test));  //[object DOMTokenList]
console.log(test);


这样,我们可以看看,classList中到底包含了什么样的值和方法。下面看来自chrome的结果:


["test", "newClass", item: function, contains: function, add: function, remove: function, toggle: function]


看上面的样式,是不是就像是一个数组?这也算是类数组了吧,既然是类数组,那么就可有一个length的属性,并且可以转换成数组,下面试一试吧:


var test = document.getElementById("test").classList,
    arr = Array.prototype.slice.call(test);
console.log(test.length); //2
console.log(typeof arr);  //object
console.log(Object.prototype.toString.call(arr));//[object Array] 
console.log(arr);//["test", "newClass"]


确定是数组了,那么为什么如itemcontainsaddremovetoggle在转换的时候,却没有了呢,继续再看一下:


var test = document.getElementById("test").classList;

console.log("item" in test); //true
console.log(test.hasOwnProperty("item"));  //false
console.log("length" in test); //true
console.log(test.hasOwnProperty("length"));  //true


这里,只拿了一个例子进行的举例,但是也可以看出,item其实是不处于实例中的,而是在原型链中的方法。

1:item

获取对应的class的值,比如上面的代码不变:


var test = document.getElementById("test").classList;

console.log(test.item(0)); //test
console.log(test.item(1)); //newClass
console.log(test.item(2)); //null


并且该方法,只能获取一个对应的class,类似数组中的item方法,所以再以数组中的操作方法试试呢


console.log(test[0]); //test
console.log(test[1]); //newClass
console.log(test[2]); //undefined


使用数组的取值方式和使用item取值的唯一区别就是,当取值超出范围之后,itme取值返回null,而数组取值返回undefined。就像是我们在真实的数组中,对一个未定义的数组元素取值时,就是返回的undefined的。

item必须要有一个输入值,如果使用test.item(),则浏览器会抛出错误。参数可以使数字或者其他,如果不是数字,则会被认为为0进行处理,比如test.item("a")的结果和test.itme(0)是相同的。

2:contains

contains这里实现的功能也就是我们之前说的hasClass的功能了,使用方法相信也可以清楚的想象了。依然上面的例子:


var test = document.getElementById("test").classList;

console.log(test.contains("test")); //true
console.log(test.contains("err"));  //false
console.log(test.contains()); //抛出错误,必须要有一个输入值。


3:add

看名字,也能想到它的功能了吧。不多说(感觉也没有什么说的),直接测试呗:


var test = document.getElementById("test").classList;

console.log(test.add("test1","test2")); //undefined
console.log(test.contains("test1")); //true


由上面的测试结果可知。add可以添加多个名称,并且add成功后,并不会有返回值告诉我们的。同时可以看出一点,classList属于实时更新的类型,这里我只在var test = document.getElementById("test").classList;进行了一次取值,在add之后,却可以直接使用contains查到新添加的class

说到这里,关于实时更新的类型,有没有突然意识到什么,为什么添加的新的class,不需要再去获取一次classList,就可以直接查到?

想想看下面的例子:


var obj = {};
obj.name = "zhang";
console.log(obj.name);


然后再对比文章关于classList最初的说明,能想通吗?因为它们都是引用类型。

4:remove

既然有add方法,那么必定有remove方法了,和add正好相反的,直接上例子:


var test = document.getElementById("test").classList;

console.log(test.remove("test","newClass")); //undefined
console.log(test.contains("test")); //false


既然已经有过了前面add方法的说明,这里也就没有什么好说的了,想同的原理呗。

5:toggle

使用过jQuery的人,都应该对toggleClass有所了解吧,当存在一个class时,则removeclass,当不存在该class时,则addclass


var test = document.getElementById("test").classList;

console.log(test.toggle("test")); //false
console.log(test.contains("test")); //false


这里让我挺觉得别扭的一点就是,为什么test.toggle返回值是false,按照返回值的情况,应该是toggle使用之后,返回的是contains的值。如果是删除掉的话,则返回false,如果是新添加的话,则返回true

并且有一点要注意的,就是toggle只支持一个参数。

OK,到这里,classList也算是结束了,只是这个属性虽然方便,可是何时才能使用啊,竟然连IE9都不支持这个方法,想想真是头大(上述都是在chrome下测试)。

结束语

话说,活到老学到老,还是需要不断的看书学习的,要么一直以一个错误的思想做事,真不是个事啊。所以继续学习下去吧,因为学习的好处太多了,比如在你打发无聊时间的同时,还在给自己涨工资呢(嘿嘿~),而且还有句话说起来感觉真的很对:越努力,越幸运。

本文地址:http://www.zhangyunling.com/?p=187

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>