[原创] jQuery源码分析-13 CSS操作-CSS-类样式-addClass+removeClass+toggleClass+hasClass

作者:nuysoft/ 高云 QQ:47214707 Email:nuysoft@gmail.com
声明:本文为原创文章,如需转载,请注明来源并保留原文链接。

jQuery 源码分析系列(持续更新)

 

类样式

 

概述

.addClass()

.removeClass()

.toggleClass()

.hasClass()

 

概述

 

本人开发时偶尔需要操作样式表,更多的是操作类样式,由 JavaScript 负责数据、业务逻辑、交互,CSS 负责呈现效果,尽可能的各司其职,减少 JavaScript 与 CSS 的耦合,便于维护。

jQuery 提供了 4 个操作 class 的方法:

jQuery.fn.extend({
	// ...
	// 为匹配的每个元素增加指定的 class(es)
	addClass: function(value) {},
	// 从匹配的每个元素上,移除 一个 或 多个 或 全部 class
	removeClass: function(value) {},
	// 对匹配元素集中的每个元素增加或删除一个或多个 class
	toggleClass: function(value, stateVal) {},
	// 检测匹配的元素是否指定了传入的 class,只要有一个匹配就返回 true
	hasClass: function(selector) {},
	// ...
});

实现的核心技巧是:先前后加空格,然后用 indexOf 查找类样式位置 或 用 replace 删除类样式。非常简洁实用,值得借鉴。

相对操作样式表要兼容浏览器差异、DOM/HTML 样式属性以及一些特殊的 bug 等等,操作类样式的实现更优雅规整一些。开始逐个学习吧。

 

.addClass()

 

.addClass() 负责为匹配的每个元素增加指定的 class(es),一次可以添加多个 class;.addClass()不会替换已有的 class 属性值,仅仅是简单的追加,重复的 class 会被过滤不会被重复追加(1.6.2 之前可能不会过滤);从 jQuery1.4 开始,.addClass() 支持参数为函数。

代码执行过程概述如下:

如果是函数,执行函数,将返回结果再次调用.addClass()

如果是字符串

    如果 className 为空,且只传入一个 className,则直接赋值给 elem.className

    否则在 elem.className 和 classNames[c] 前后加空格,判断 indexOf 的返回值,是忽略还是追加

核心技巧:前后加空格 + indexOf,详看源码分析

/**
 * 为匹配的每个元素增加指定的 class(es)
 * .addClass(className)
 *   className 添加到每个匹配元素的 class 属性上的一个或多个 class
 *   
 * .addClass(function(index, currentClass) )*   function(index, currentClass) 返回一个或多个 class 名称,多个 class 用空格分开,这些 class 被添加到现有的 class 属性中
 *   index 当前元素在集合中的位置,currentClass 当前的 class 名,this 指向集合中的当前元素
 * 核心技巧:前后加空格 + indexOf
 */
addClass: function(value) {
	/*
	 * 从 1.6.2 开始,这些局部变量被提取到方法的头部,似乎这是 jQuery 一直以来的习惯:不断的重构代码
	 * 我个人是反对这种集中定义变量的写法的,一个很明显的理由是:
	 * 我往下读的过程中,遇到没看懂的变量,我需要跳到方法头来理解和验证,然后我再跳回去
	 */
	var classNames, i, l, elem,
		setClass, c, cl;
<span style="color: rgba(0, 128, 0, 1)">// 如果传入函数则执行函数,取返回值作为要设置的classNames</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> ( jQuery.isFunction( value ) ) {
	<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span>.each(<span style="color: rgba(0, 0, 255, 1)">function</span>( j ) {
		<span style="color: rgba(0, 128, 0, 1)">/*
		 * 迭代调用
		 * 在1.6.2以前的版本中会创建var self = jQuery(this);
		 * 通过self.attr("class") || "" 获取当前的class值
		 * 从1.6.2开始,使用this.className来获取
		 * 稍微提高性能,但是之前为什么调用attr呢?不理解
		 * 
		 * 另外要注意到,没有jQuery.addClass函数,
		 * 事实上用.addClass()调用jQuery.addClass()这种写法,可以避免构建新的jQuery对象
		 * 可能创建一个jQuery.addClass,然后复用的地方很少,就全部在jQuery.fn.addClass中实现了
		 */</span>
		jQuery( <span style="color: rgba(0, 0, 255, 1)">this</span> ).addClass( value.call(<span style="color: rgba(0, 0, 255, 1)">this</span>, j, <span style="color: rgba(0, 0, 255, 1)">this</span>.className) );
	});
}

<span style="color: rgba(0, 128, 0, 1)">// 如果value是字符串,可以看到.addClass()只接受字符串和函数</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> ( value &amp;&amp; <span style="color: rgba(0, 0, 255, 1)">typeof</span> value === "<span style="color: rgba(139, 0, 0, 1)">string</span>" ) {
	classNames = value.split( rspace ); <span style="color: rgba(0, 128, 0, 1)">// 用空白符分割classNames,转换为数组</span>

	<span style="color: rgba(0, 0, 255, 1)">for</span> ( i = 0, l = <span style="color: rgba(0, 0, 255, 1)">this</span>.<span style="color: rgba(0, 0, 255, 1)">length</span>; i &lt; l; i++ ) { <span style="color: rgba(0, 128, 0, 1)">// 遍历所有的匹配元素,缓存长度length</span>
		elem = <span style="color: rgba(0, 0, 255, 1)">this</span>[ i ]; <span style="color: rgba(0, 128, 0, 1)">// 缓存下来,避免再次查找</span>

		<span style="color: rgba(0, 0, 255, 1)">if</span> ( elem.nodeType === 1 ) { <span style="color: rgba(0, 128, 0, 1)">// Element</span>
			<span style="color: rgba(0, 128, 0, 1)">/*
			 * 如果没有在HTML中指定class属性,或class属性为空字符串
			 * 从1.6.2开始增加判断条件classNames.length === 1,多于一个需要去重
			 * 1.6.2之前未对classNames的长度做判断,即没有去重
			 * 
			 * 在Chrome15中测试,未指定class的div,它的className返回空字符串""
			 */</span>
			<span style="color: rgba(0, 0, 255, 1)">if</span> ( !elem.className &amp;&amp; classNames.<span style="color: rgba(0, 0, 255, 1)">length</span> === 1 ) {
				elem.className = value;
			<span style="color: rgba(0, 128, 0, 1)">// 已有className 或 classNames长度大于1</span>
			} <span style="color: rgba(0, 0, 255, 1)">else</span> {
				<span style="color: rgba(0, 128, 0, 1)">/*
				 * 前后加空格,能正确的通过indexOf判断
				 * 这里先将elem.className取出来缓存起来,拼装完后再一次性赋值
				 * 避免因多次修改className造成浏览器多次渲染
				 */</span>
				setClass = "<span style="color: rgba(139, 0, 0, 1)"> </span>" + elem.className + "<span style="color: rgba(139, 0, 0, 1)"> </span>";

				<span style="color: rgba(0, 0, 255, 1)">for</span> ( c = 0, cl = classNames.<span style="color: rgba(0, 0, 255, 1)">length</span>; c &lt; cl; c++ ) {
					<span style="color: rgba(0, 128, 0, 1)">/*
					 * 关于~,摘自《JavaScript权威指南 5th》
					 * ~ 按位非运算符,~是一元运算符,位于一个整形参数前,将运算数的所有位取反。
					 * 相当于改变它的符号并且减一。
					 * 其实这里简单的简单的对indexOf的返回值判断即可,小于0表示不存在
					 * 不存在,则追加到setClass后
					 * 
					 * 测试:
					 * ~-1 == 0;		~0 == -1; 		~1 == 2; 		~2 == -3
					 * !~-1 == true;	!~0 == fase;	!~1 == false; 	~2 == false
					 * 
					 * 所以if的判断逻辑是:不存在(-1)返回true,其他情况都返回false
					 * 从1.6.2开始,这里变风骚了;忍不住想测试验证一下:
					 * &lt;pre&gt;
					 * var count = 100000;
					 * console.time('1yuan'); for( i = 0; i &lt; count; i++ ) !~-1; console.timeEnd('1yuan')
					 * console.time('2yuan'); for( i = 0; i &lt; count; i++ ) 1 &lt; -1; console.timeEnd('2yuan')
					 * &lt;/pre&gt;
					 * 这个case很简单,将测试用例反复运算、调整顺序运算,并没有发现一元运算符比二元运算符快!
					 * 有待继续挖掘!不排除John Resig开了个玩笑。真心不能排除John Resig偶尔调皮一下的可能性!
					 */</span>
					<span style="color: rgba(0, 0, 255, 1)">if</span> ( !~setClass.indexOf( "<span style="color: rgba(139, 0, 0, 1)"> </span>" + classNames[ c ] + "<span style="color: rgba(139, 0, 0, 1)"> </span>" ) ) {
						setClass += classNames[ c ] + "<span style="color: rgba(139, 0, 0, 1)"> </span>"; <span style="color: rgba(0, 128, 0, 1)">// 追加,最后加一个空格</span>
					}
				}
				<span style="color: rgba(0, 128, 0, 1)">/*
				 * 去掉前后的空白符
				 * trim中将替换过程分为替换前空白符和替换后空白符两步
				 * 事实上除了在trim中,我也没发现有其他代码用用到了trimLeft trimRight
				 * 如果单考虑效率的话,合并一起来更快
				 * rtrim = /^\s+|\s+$/;
				 * text.toString().replace( rtrim, "" );
				 * 这么分开可能是为了潜在的复用
				 * 因此性能不是唯一的追求,这是John Resig在可读性、复用粒度、性能之间的权衡
				 */</span>
				elem.className = jQuery.trim( setClass );
			}
		}
	}
}

<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span>;

},

 

.removeClass()

 

.removeClass() 从匹配的每个元素上,移除 一个 或 多个 或 全部 class;如果传入一个 class 参数,只有这个 class 会被移除;如果没有指定任何参数,所有的 class 会被移除;一次可以移除多个 class,多个 class 之间用空格分隔;这个方法经常与.addClass 配合使用,用来切换元素的 class 从一个变为另一个;如果要用一个 class 替换所有的现有 class,可以使用.attr('class', 'newClass') 代替;从 jQuery1.4 开始,.removeClass() 方法支持传入一个函数作为参数。

代码执行过程概述如下:

如果是函数,执行函数,将返回结果再次调用 jQuery.fn.removeClass

如果是字符串,在 elem.className 和 classNames[c] 前后加空格,判断 replace 删除

如果是 undefined,置为空字符串 elem.className = ""

核心技巧:前后加空格 + replace,详看源码分析

/**
 * 从匹配的每个元素上,移除 一个 或 多个 或 全部 class
 * 
 * .removeClass([className] )
 *   className 一个或多个以空格分隔的 class,这些 class 将被从匹配元素的 class 属性中溢出
 *   
 * .removeClass(function(index, class) )*   function(index, class) 函数返回一个或多个以空格分隔的 class,用于移除。
 *   index 当前元素在匹配元素集合中的位置, class 旧 class 值
* 核心技巧:前后加空格 + replace
 */
removeClass: function(value) {
	var classNames, i, l, elem, className, c, cl;
 <span style="color: rgba(0, 128, 0, 1)">// 如果传入函数则执行函数,取返回值作为要移除的classNames</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> ( jQuery.isFunction( value ) ) {
	<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span>.each(<span style="color: rgba(0, 0, 255, 1)">function</span>( j ) {
		<span style="color: rgba(0, 128, 0, 1)">// 迭代调用,见.addClass()的注释</span>
		jQuery( <span style="color: rgba(0, 0, 255, 1)">this</span> ).removeClass( value.call(<span style="color: rgba(0, 0, 255, 1)">this</span>, j, <span style="color: rgba(0, 0, 255, 1)">this</span>.className) );
	});
}
<span style="color: rgba(0, 128, 0, 1)">/*
 * 对比.addClass()的条件:if ( value &amp;&amp; typeof value === "string" )
 * 从这里可以看出.removeClass()支持的参数类型:
 * 函数			迭代处理
 * 非空字符串	移除
 * undefined	全部移除
 * 注:空字符串不做任何处理
 */</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> ( (value &amp;&amp; <span style="color: rgba(0, 0, 255, 1)">typeof</span> value === "<span style="color: rgba(139, 0, 0, 1)">string</span>") || value === undefined ) {
	classNames = ( value || "<span style="color: rgba(139, 0, 0, 1)"></span>" ).split( rspace ); <span style="color: rgba(0, 128, 0, 1)">// 分割成数组</span>
	<span style="color: rgba(0, 128, 0, 1)">// value || "" 避免空引用错误的常用技巧(ReferenceError: value is undefined)</span>

	<span style="color: rgba(0, 0, 255, 1)">for</span> ( i = 0, l = <span style="color: rgba(0, 0, 255, 1)">this</span>.<span style="color: rgba(0, 0, 255, 1)">length</span>; i &lt; l; i++ ) { <span style="color: rgba(0, 128, 0, 1)">// 遍历匹配的元素,缓存集合长度 </span>
		elem = <span style="color: rgba(0, 0, 255, 1)">this</span>[ i ];
		 <span style="color: rgba(0, 128, 0, 1)">// Element,并且有className属性,没有className就不需要删除</span>
		<span style="color: rgba(0, 0, 255, 1)">if</span> ( elem.nodeType === 1 &amp;&amp; elem.className ) {
			<span style="color: rgba(0, 128, 0, 1)">// 如果有value,则从当前的className属性中删除</span>
			<span style="color: rgba(0, 0, 255, 1)">if</span> ( value ) {
				className = ("<span style="color: rgba(139, 0, 0, 1)"> </span>" + elem.className + "<span style="color: rgba(139, 0, 0, 1)"> </span>").replace( rclass, "<span style="color: rgba(139, 0, 0, 1)"> </span>" ); <span style="color: rgba(0, 128, 0, 1)">// 前后加空格,将\n\t\r替换为空格</span>
				<span style="color: rgba(0, 0, 255, 1)">for</span> ( c = 0, cl = classNames.<span style="color: rgba(0, 0, 255, 1)">length</span>; c &lt; cl; c++ ) {
					className = className.replace("<span style="color: rgba(139, 0, 0, 1)"> </span>" + classNames[ c ] + "<span style="color: rgba(139, 0, 0, 1)"> </span>", "<span style="color: rgba(139, 0, 0, 1)"> </span>"); <span style="color: rgba(0, 128, 0, 1)">// 将要删除的className替换为空格</span>
				}
				<span style="color: rgba(0, 128, 0, 1)">// 删除前后的空白符,然后赋值给elem.className</span>
				elem.className = jQuery.trim( className );
			<span style="color: rgba(0, 128, 0, 1)">// 没有指定value undefined,清空className属性</span>
			} <span style="color: rgba(0, 0, 255, 1)">else</span> {
				<span style="color: rgba(0, 128, 0, 1)">// 清空</span>
				elem.className = "<span style="color: rgba(139, 0, 0, 1)"></span>";
			}
		}
	}
}

<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span>;

},

 

.toggleClass()

 

.toggleClass() 负责对匹配元素集中的每个元素增加或删除一个或多个 class,增加或删除的行为依赖当前元素是否含有指定的 class 或 switch 参数的值;.toggleClass()接受一个或多个 class;自从 jQuery1.4 以后,如果没有为.toggleClass() 指定参数,元素上的所有 class 名称将被切换;自从 jQuery1.4 以后,className 可以是一个函数,函数的返回值作为切换的 className。

代码执行过程概述如下:

如果是函数,则执行函数,用函数的返回值作为切换的 className,迭代调用 jQuery.fn.toggleClass

遍历当前 jQuery 对象

    如果 value 是字符串,挨个遍历 value 中的类样式,switch 的优先级高于 hasClass,hasClass 返回 false 则 addClass 返回 true 则 removeClass

    如果 未指定参数 或 只有 switch,则切换整个 className

核心技巧:调用 addClass 或 removeClass 或 直接赋值 elem.className。

使用时要注意:在看源码的过程中发现switch在不同的参数环境下功能不一致。.toggleClass() 用 4 种用法(详见后文源码注释):

1  .toggleClass(className) 1.0
2  .toggleClass( className, switch ) 1.3
3  .toggleClass( [switch] ) 1.4
4  .toggleClass( function(index, class, switch) [, switch] ) 1.4

在 2 和 4 中依据 switch 来决定是添加(true)还是删除(false),例如:

$('#foo').toggleClass(className, addOrRemove);
// 本质上等价于:
if (addOrRemove) $('#foo').addClass(className);
else $('#foo').removeClass(className);

但是在 3 中,如果 switch 为 true,进行正常的全部切换,等价于 undefined;如果 switch 为 false,则总是置空;事实上,这一点在官方 API 中并没有提及。这一逻辑在最后一行的三元表达式中得到体现。

详看源码分析(从代码技巧的风骚度看,个人觉得最精妙的是最后一行代码):

/**
 * 对匹配元素集中的每个元素增加或删除一个或多个 class
 * 增加或删除的行为依赖当前元素是否含有指定的 class,或 switch 参数的值
 * 
 * .toggleClass(className) 1.0
 *   className 一个或多个 class(用空格隔开),在匹配元素集的每个元素上切换 class
 *   如果集合中的某个元素含有指定的 className,className 会被删除;如果没有会添加。
 *   
 * .toggleClass(className, switch) 1.3
 *   switch 一个布尔值,依据这个布尔值来决定是添加(true)还是删除(false)
* 
 * .toggleClass([switch] ) 1.4
 *   switch 一个布尔值,依据这个布尔值来决定是添加还是删除
*   
 * .toggleClass(function(index, class, switch) [, switch] ) 1.4
 *   function(index, class, switch) 函数返回用于切换的 calss 名称
 *   index 是当前元素是集合中的下标位置, class 是当前元素的就 class 值
 *   
* 核心技巧:调用 addClass 或 removeClass 或 直接赋值 elem.className
*/
toggleClass: function(value, stateVal) {
	var type = typeof value, // value 的类型,可以是字符串(一个或多个 class),也可以是 function,(undefined 和 boolean 是另说)
		isBool = typeof stateVal === "boolean";
 <span style="color: rgba(0, 128, 0, 1)">// 如果是函数,则执行函数,用函数的返回值作为切换的className,迭代调用jQuery.fn.toggleClass</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> ( jQuery.isFunction( value ) ) {
	<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span>.each(<span style="color: rgba(0, 0, 255, 1)">function</span>( i ) {
		<span style="color: rgba(0, 128, 0, 1)">// 迭代调用</span>
		jQuery( <span style="color: rgba(0, 0, 255, 1)">this</span> ).toggleClass( value.call(<span style="color: rgba(0, 0, 255, 1)">this</span>, i, <span style="color: rgba(0, 0, 255, 1)">this</span>.className, stateVal), stateVal );
	});
}

<span style="color: rgba(0, 128, 0, 1)">// 遍历当前jQuery对象</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span>.each(<span style="color: rgba(0, 0, 255, 1)">function</span>() {
	<span style="color: rgba(0, 128, 0, 1)">// value是字符串,挨个遍历value中的类样式,switch的优先级高于hasClass,hasClass返回false则addClass返回true则removeClass</span>
	<span style="color: rgba(0, 0, 255, 1)">if</span> ( type === "<span style="color: rgba(139, 0, 0, 1)">string</span>" ) {
		<span style="color: rgba(0, 128, 0, 1)">// toggle individual class names</span>
		<span style="color: rgba(0, 128, 0, 1)">// 切换单个class</span>
		<span style="color: rgba(0, 0, 255, 1)">var</span> className,
			i = 0,
			<span style="color: rgba(0, 0, 255, 1)">self</span> = jQuery( <span style="color: rgba(0, 0, 255, 1)">this</span> ),
			state = stateVal,
			classNames = value.split( rspace ); <span style="color: rgba(0, 128, 0, 1)">// 可能有多个class,用空白符分割</span>
		<span style="color: rgba(0, 128, 0, 1)">// 因为不需要在className前后加空格,所以这里可以将取值、自增、判断合并为while循环。很好的技巧。</span>
		<span style="color: rgba(0, 0, 255, 1)">while</span> ( (className = classNames[ i++ ]) ) {
			<span style="color: rgba(0, 128, 0, 1)">// check each className given, space seperated list</span>
			<span style="color: rgba(0, 128, 0, 1)">/*
			 * 如果state是布尔值,则以state为准,否则检查是否含有className
			 * 含有 state为false,表示需要addClass;反之需要removeClass
			 * 这个三元表达式合并了state与self.hasClass的判断,小技巧
			 */</span>
			state = isBool ? state : !<span style="color: rgba(0, 0, 255, 1)">self</span>.hasClass( className );
			<span style="color: rgba(0, 0, 255, 1)">self</span>[ state ? "<span style="color: rgba(139, 0, 0, 1)">addClass</span>" : "<span style="color: rgba(139, 0, 0, 1)">removeClass</span>" ]( className );
		}
	<span style="color: rgba(0, 128, 0, 1)">/*
	 * type === "undefined"	未指定参数,即.toggleClass()
	 * type === "boolean" 	省略className,只有switch,即.toggleClass( switch )
	 */</span>
	<span style="color: rgba(0, 128, 0, 1)">// 未指定参数 或 只有switch,则切换整个className</span>
	} <span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> ( type === "<span style="color: rgba(139, 0, 0, 1)">undefined</span>" || type === "<span style="color: rgba(139, 0, 0, 1)">boolean</span>" ) {
		 <span style="color: rgba(0, 128, 0, 1)">// 如果有className,则缓存下来,以便再次调用时恢复</span>
		<span style="color: rgba(0, 0, 255, 1)">if</span> ( <span style="color: rgba(0, 0, 255, 1)">this</span>.className ) {
			<span style="color: rgba(0, 128, 0, 1)">// store className if set</span>
			<span style="color: rgba(0, 128, 0, 1)">// 以内部数据的方式缓存</span>
			jQuery._data( <span style="color: rgba(0, 0, 255, 1)">this</span>, "<span style="color: rgba(139, 0, 0, 1)">__className__</span>", <span style="color: rgba(0, 0, 255, 1)">this</span>.className );
		}

		<span style="color: rgba(0, 128, 0, 1)">// toggle whole className</span>
		<span style="color: rgba(0, 128, 0, 1)">/*
		 * 切换整个className
		 * 又是一个合并了几个判断条件的三元,分解为四个逻辑:
		 * this.className &amp;&amp; value 是 true/undefined 	""
		 * this.className &amp;&amp; value 是 false 			""
		 * !this.className &amp;&amp; value 是 true/undefined	jQuery._data( this, "__className__" ) || ""
		 * !this.className &amp;&amp; value 是 false 			""
		 * 
		 * 分析一下上边的四个逻辑,可以总结如下:(value用switch代替)
		 * 1. 如果this.className存在,无论switch什么状态(true/false/undefined),都置为空""
		 * 2. 如果this.className不存在,如果switch为true/undefined,才会恢复className
		 * 3. 如果this.className不存在,如果switch为false,置空(保持不变)
		 * 
		 * .toggleClass( switch )的用法可以总结如下:
		 * 1. switch为true,进行正常的切换,等价于.toggleClass()
		 * 2. switch为false,总是置空
		 * 
		 * 一开始真心看不懂,很精致很风骚!
		 */</span>
		<span style="color: rgba(0, 0, 255, 1)">this</span>.className = <span style="color: rgba(0, 0, 255, 1)">this</span>.className || value === <span style="color: rgba(0, 0, 255, 1)">false</span> ? "<span style="color: rgba(139, 0, 0, 1)"></span>" : jQuery._data( <span style="color: rgba(0, 0, 255, 1)">this</span>, "<span style="color: rgba(139, 0, 0, 1)">__className__</span>" ) || "<span style="color: rgba(139, 0, 0, 1)"></span>";
	}
});

},


 

.hasClass()

 

.hasClass() 检测匹配的元素是否指定了传入的 class,只要有一个匹配就返回 true;元素可能有多个 class,在 HTML 中多个 class 用空格隔开;如果遇到某个元素含有指定的 className,.hasClass() 将会返回 true,即便还指定了其他的 className。

注意:

1. 如果 selector 自带前 / 后空格,就不能正确的检测,事实上这里可以过滤前后空格,然后再检测,这样在动态的检测 className 时,代码更灵活

2. selector 可以含有多个类样式,但不会拆分为数组而是作为整体检测,例如下面的情况就不能返回正确的结果:

$('div').addClass('1 2 3').hasClass('1 3') // false

核心技巧:前后加空格 + indexOf

源码分析

/**
* 检测匹配的元素是否指定了传入的 class,只要有一个匹配就返回 true
 * .hasClass(className)
 *   className 要查找的 class
* 核心技巧:前后加空格 + indexOf
 */
hasClass: function(selector) {
	var className = " "+ selector +" ", // 前后加空格
		i = 0,
		l = this.length;
	for (; i < l; i++) {
		 // 必须是 Element,技巧同样是前后加空格,同样是 indexOf
		if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass," ").indexOf( className) > -1 ) {
			return true;
		}
	}
<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span>;

},