jQuery源码解析之addClass(),removeClass(),toggleClass()和hasClass()
一、$()
.addClass()
作用:
向目标元素添加一个或多个类名
源码:
//向目标元素添加一个或多个类名 //源码8401行 addClass: function( value ) { var classes, elem, cur, curValue, clazz, j, finalValue, i = 0; //如果addClass(value)的value是一个function //那么就通过call让目标元素this调用该function if ( isFunction( value ) ) { return this.each( function( j ) { // function(index,currentclass) // index 对应 j,作用是获取多个目标元素的下标; // currentClass 对应 getClass(this),作用是获取当前元素的类名,方便加空格 jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); } ); } //将(多个)类名转为数组形式 classes = classesToArray( value ); if ( classes.length ) { //多个目标元素 while ( ( elem = this[ i++ ] ) ) { //获取当前值 curValue = getClass( elem ); //如果目标元素是元素节点并且用空格左右包起来 " "+value+" " cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { j = 0; //一个个类名 while ( ( clazz = classes[ j++ ] ) ) { //当前元素没有和要添加的类名重复的话就添加 if ( cur.indexOf( " " + clazz + " " ) < 0 ) { cur += clazz + " "; } } //最后,确认经过处理后的类名集合是否和处理前的类名集合相同 //如果相同,就setAttribute,重新渲染,否则不重新渲染(性能优化) // Only assign if different to avoid unneeded rendering. finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { //最后通过setAttribute,添加类名 elem.setAttribute( "class", finalValue ); } } } } return this; },
解析:
(1)getClass()
作用:
获取目标元素的类名
源码:
//源码8377行 function getClass( elem ) { return elem.getAttribute && elem.getAttribute( "class" ) || ""; }
(2)classesToArray
作用:
将(多个)类名转为数组形式
源码:
//源码8382行 function classesToArray( value ) { //元素的className如果有多个类名的话,是以数组形式保存的,那就直接返回 if ( Array.isArray( value ) ) { return value; } //如果元素类名是string类型的话 if ( typeof value === "string" ) { return value.match( rnothtmlwhite ) || []; } return []; }
(3)stripAndCollapse
作用:
将vaues以空格分开,再以空格拼接
源码:
// 源码8370行 // Strip and collapse whitespace according to HTML spec // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace function stripAndCollapse( value ) { var tokens = value.match( rnothtmlwhite ) || []; return tokens.join( " " ); }
综上:
可以看到 addClass() 的思路是:
① 获取元素当前类名集合 a
② 如果要添加的类名 b 不重复,则将 b 添加进 a 里
③ 最后使用elem.setAttribute("class",a)
完成
二、$()
.removeClass
作用:
移除类,如果没有参数,则移除目标元素所有类名
源码:
//源码8449行 removeClass: function( value ) { var classes, elem, cur, curValue, clazz, j, finalValue, i = 0; //作用同上 if ( isFunction( value ) ) { return this.each( function( j ) { jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); } ); } //如果没有规定参数,则删除所有类 if ( !arguments.length ) { return this.attr( "class", "" ); } //将(多个)类名转为数组形式 classes = classesToArray( value ); if ( classes.length ) { while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); // This expression is here for better compressibility (see addClass) cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { j = 0; while ( ( clazz = classes[ j++ ] ) ) { // 如果当前元素的类名里有要移除的类, // 就将该类替换成" " // Remove *all* instances while ( cur.indexOf( " " + clazz + " " ) > -1 ) { cur = cur.replace( " " + clazz + " ", " " ); } } //同上 // Only assign if different to avoid unneeded rendering. finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { elem.setAttribute( "class", finalValue ); } } } } return this; },
可以看到 addClass() 的思路是:
① 获取元素当前类名集合 a
② 如果要移除的类名 b 不重复,则将 a 里面的 b 替换成空格 " "
③ 最后使用elem.setAttribute("class",a)
完成移除类名
三、$()
.toggleClass
作用:
切换类
源码:
//stateVal为true,则添加类,false则移除类 //源码8497行 toggleClass: function( value, stateVal ) { var type = typeof value, //如果value是string类型或者是数组类型的话,为true,反之为false isValidValue = type === "string" || Array.isArray( value ); //true添加类,false移除类 if ( typeof stateVal === "boolean" && isValidValue ) { return stateVal ? this.addClass( value ) : this.removeClass( value ); } //同上 if ( isFunction( value ) ) { return this.each( function( i ) { jQuery( this ).toggleClass( value.call( this, i, getClass( this ), stateVal ), stateVal ); } ); } return this.each( function() { var className, i, self, classNames; if ( isValidValue ) { // Toggle individual class names i = 0; self = jQuery( this ); classNames = classesToArray( value ); while ( ( className = classNames[ i++ ] ) ) { //如果目标元素已经有要toggle的className,那么就移除它 // Check each className given, space separated list if ( self.hasClass( className ) ) { self.removeClass( className ); } else { //否则就添加类 self.addClass( className ); } } // Toggle whole class name } //如果$.toggleClass()没有值或者该值为布尔值 else if ( value === undefined || type === "boolean" ) { className = getClass( this ); //如果目标元素有类的话,就先用__className__属性保存类名 if ( className ) { // Store className if set dataPriv.set( this, "__className__", className ); } // If the element has a class name or if we're passed `false`, // then remove the whole classname (if there was one, the above saved it). // Otherwise bring back whatever was previously saved (if anything), // falling back to the empty string if nothing was stored. //如果目标元素存在setAttribute的方法话 if ( this.setAttribute ) { //如果已有类名/value=false,则移除所有类名 //如果没有类名并且value=true, //则从dataPriv中重新获取之前保存过的__className__当做目标元素的类名 this.setAttribute( "class", className || value === false ? "" : dataPriv.get( this, "__className__" ) || "" ); } } } ); },
解析:
主要是两个 if
(1) if ( typeof stateVal === "boolean" && isValidValue )
这个就是$()
.toggleClass(value,truefalse) 的第二个参数的作用了,
true 即 addClass(),false 即 removeClass()
(2)if(isValidValue )
这个是只有一个参数的情况
利用 hasClass 判断是否 add/removeClass
(3)如果$.toggleClass()没有值或者第一个值为 true 的话
如果目标元素有类名的话,就使用dataPriv
来保存类名,
如果目标元素有setAttribute
的话,则将 className 设置为dataPriv
里保存的值。
四、$()
.hasClass
作用:
检查目标元素是否包含指定的类
源码:
//源码8568行 hasClass: function( selector ) { var className, elem, i = 0; className = " " + selector + " "; while ( ( elem = this[ i++ ] ) ) { if ( elem.nodeType === 1 && //关键代码 ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { return true; } } return false; }
这个代码还挺简单的,就不解析了。
(完)