jQuery源码分析之addClass,removeClass,toggleClass,hasClass详解四问

测试代码1:获取元素的class数组

var cs="name sex"
alert({}.toString.call(cs.match(/\S+/g)));
//返回[object Array],length是2.默认是空数组[],数组元素默认分隔符是逗号!
测试代码2:正则表达式中的空白符

<p id="p" class="params qinliang">This is a paragraph.</p>
alert(document.getElementById("p").className);//输出"params qinliang"  
 var rclass = /[\t\r\n\f]/g;  //\t为制表符,\r回车符,\n换行符,\f换页符
测试代码3:空格在if语句中会转化为true

//空格会变成true,所以执行下面的if语句
  if(" ")
{
  alert("空格变成true");//打印true
}else
{
 alert("空格变成false");
}
问题1:addClass的源码是什么?

addClass: function( value ) {
		var classes, elem, cur, clazz, j, finalValue,
			i = 0,
			len = this.length,
			proceed = typeof value === "string" && value;
		if ( jQuery.isFunction( value ) ) {
			return this.each(function( j ) {
				jQuery( this ).addClass( value.call( this, j, this.className ) );
			});
		}
		if ( proceed ) {//如果传入的参数是String,我们把它切分为数组!
			// The disjunction here is for better compressibility (see removeClass)
			classes = ( value || "" ).match( rnotwhite ) || [];
			for ( ; i < len; i++ ) {
				elem = this[ i ];
				cur = elem.nodeType === 1 && ( elem.className ?
					( " " + elem.className + " " ).replace( rclass, " " ) :
					" "
				);
				if ( cur ) {
					j = 0;
					while ( (clazz = classes[j++]) ) {
						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
							cur += clazz + " ";
						}
					}
					// only assign if different to avoid unneeded rendering.
					finalValue = jQuery.trim( cur );
					if ( elem.className !== finalValue ) {
						elem.className = finalValue;
					}
				}
			}
		}

		return this;
	}
我们来根据上面的代码举例看看不同情况是怎么调用的

情况1:如果我们传入了函数,那么上下文是调用对象的DOM,第一个参数是该DOM在调用对象的下标,第二个参数表示该元素的className属性

if ( jQuery.isFunction( value ) ) {
			return this.each(function( j ) {
				//如果addClass传入函数,那么我们把函数执行的结果作为class的值添加到元素的class集合中
				//在回调函数中上下文是调用对象的DOM元素,第一个参数是该DOM下标(each第一个参数是下标)
				//第二个参数该元素的className
				jQuery( this ).addClass( value.call( this, j, this.className ) );
			});
		}
我们通过这种方式来改变body元素的背景色

$("body").addClass(function(index,classN)
	{
	  return "qinliang"+index;
	  //回调函数中第一个参数是该DON下标,第二个参数是该DOM的className值!
	});
情况2:我们把元素本来的className前后各添加一个空格,同时把回车符,换行符等全部替换掉

cur = elem.nodeType === 1 && ( elem.className ?
					( " " + elem.className + " " ).replace( rclass, " " ) :
					" "
				);
这样做的目地是为了后面判断添加的class是否已经在原来元素的className中,如果在那么我们不会添加,可以减少回流!

情况3:判断添加的class是否已经在元素的className中,如果在不添加,添加的时候后面还要添加一个空格!

	                           while ( (clazz = classes[j++]) ) {
						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
							cur += clazz + " ";
						}
情况4:如果都添加以后className还是和以前一样,那么我们不修改className,防止没有改变的时候有页面回流和重绘!

                             finalValue = jQuery.trim( cur );
					if ( elem.className !== finalValue ) {
						elem.className = finalValue;
					}
问题2:removeClass的源码是什么?

removeClass: function( value ) {
		var classes, elem, cur, clazz, j, finalValue,
			i = 0,
			len = this.length,
			proceed = arguments.length === 0 || typeof value === "string" && value;

		if ( jQuery.isFunction( value ) ) {
			return this.each(function( j ) {
				jQuery( this ).removeClass( value.call( this, j, this.className ) );
			});
		}
		if ( proceed ) {
			classes = ( value || "" ).match( rnotwhite ) || [];
			for ( ; i < len; i++ ) {
				elem = this[ i ];
				// This expression is here for better compressibility (see addClass)
				cur = elem.nodeType === 1 && ( elem.className ?
					( " " + elem.className + " " ).replace( rclass, " " ) :
					""
				);
				if ( cur ) {
					j = 0;
					while ( (clazz = classes[j++]) ) {
						// Remove *all* instances
						while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
							cur = cur.replace( " " + clazz + " ", " " );
						}
					}

					// only assign if different to avoid unneeded rendering.
					finalValue = value ? jQuery.trim( cur ) : "";
					if ( elem.className !== finalValue ) {
						elem.className = finalValue;
					}
				}
			}
		}

		return this;
	}
 removeClass和addClass等都是可以同时添加或者删除多个class值,这时候把值之间用 空格隔开就可以了!
情况1:传入的class是函数,我们把函数调用的结果作为class移除

if ( jQuery.isFunction( value ) ) {
			return this.each(function( j ) {
				jQuery( this ).removeClass( value.call( this, j, this.className ) );
			});
		}
情况2:在开始移除之前,我们把原来的className前后添加空格,同时把回车等全部用空格替换,以便后面用className来进行class的查找

        // This expression is here for better compressibility (see addClass)
				cur = elem.nodeType === 1 && ( elem.className ?
					( " " + elem.className + " " ).replace( rclass, " " ) :
					""
				);
情况3:用indexOf判断要删除的class是否在元素原来的class中,如果在,我们就删除他,也就是替换为空格!

                                while ( (clazz = classes[j++]) ) {
						// Remove *all* instances
						while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
							cur = cur.replace( " " + clazz + " ", " " );
						}
					}
情况4:只有前后的class有变化的时候才会修改className值,这样可以防止多余的重绘和回流!

                             finalValue = value ? jQuery.trim( cur ) : "";
					if ( elem.className !== finalValue ) {
						elem.className = finalValue;
					}
问题3:hasClass的源码如何?

	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;
	}
通过源码我们知道,hasClass如果判断多于一个的className的值时候,只有参数顺序和原来的className中多个 参数顺序完全一致才可以;同时该方法表明, 只要有一个调用对象的DOM元素包含了这个参数class就会返回true!
问题4:toggleClass源码如何?

toggleClass: function( value, stateVal ) {
		var type = typeof value;
		if ( typeof stateVal === "boolean" && type === "string" ) {
			return stateVal ? this.addClass( value ) : this.removeClass( value );
		}
		if ( jQuery.isFunction( value ) ) {
			return this.each(function( i ) {
				jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
			});
		}
		return this.each(function() {
			if ( type === "string" ) {
				// toggle individual class names
				var className,
					i = 0,
					self = jQuery( this ),
					classNames = value.match( rnotwhite ) || [];
				while ( (className = classNames[ i++ ]) ) {
					// check each className given, space separated list
					if ( self.hasClass( className ) ) {
						self.removeClass( className );
					} else {
						self.addClass( className );
					}
				}
			// Toggle whole class name
			} else if ( type === strundefined || type === "boolean" ) {
				if ( this.className ) {
					// store className if set
					jQuery._data( this, "__className__", this.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.
				this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
			}
		});
	}
情况1:第一个参数是string,第二个参数是boolean, 这时候如果boolean是true那么添加指定的类型,如果是false表示移除指定的类名

               var type = typeof value;
		//如果第一个参数是string,第二个参数是boolean,如果boolean是true那么添加,否则移除!
		if ( typeof stateVal === "boolean" && type === "string" ) {
			return stateVal ? this.addClass( value ) : this.removeClass( value );
		}
情况2:如果第一个参数是函数,那么我们直接调用该函数,把调用的返回值作为第一个参数继续调用toggleClass

if ( jQuery.isFunction( value ) ) {
			return this.each(function( i ) {
				jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
			});
		}
此时上下文是DOM,第一个参数是该DOM下标,第二个参数是该元素的className,第三个参数是我们传入的第二个参数!
情况3:如果调用toggleClass时候传入第一个参数是string,那么表示我们希望对这些class进行toggle

                        if ( type === "string" ) {
				// toggle individual class names
				var className,
					i = 0,
					self = jQuery( this ),
					//toggleClass如果传入多个calss,其中用空格分开,得到一个需要toggle的class列表!
				classNames = value.match( rnotwhite ) || [];
				while ( (className = classNames[ i++ ]) ) {
					// check each className given, space separated list
					if ( self.hasClass( className ) ) {
						self.removeClass( className );
					} else {
						self.addClass( className );
					}
				}
			// Toggle whole class name
			} 
这时候如果元素有这些class,那么我们移除这些class,如果没有那么添加!
情况4:如果没有传入参数或者传入的参数只有一个,同时是boolean,那么我们要做特殊处理,修改整个class的值
如果有className,那么我们把className的值保存到内部数据中,同时把className设置为空字符串

如果没有className,同时value不是false,那么我们读取刚才保存到该元素的内部数据className!

                        if ( type === strundefined || type === "boolean" ) {
				if ( this.className ) {
					// store className if set
					jQuery._data( this, "__className__", this.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.
				this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
			}
测试用例1:我们连续调用toggleClass()来进行切换

        $('body').toggleClass();
	//这时候就会把body元素的class全部保存到body的内部数据中,同时把class设置为空字符串!
	var expando=jQuery.expando;
	var key=$('body')[0][expando];
	var walhouse=jQuery.cache;
	var data=walhouse[key];
	//这时候是jQuery._data,所以是内部数据,不会保存在data域下面
	//打印Object {__className__: "body qinliang0"}
	console.log(data);
	//这时候继续调用toggleClass不传入参数,就会把刚才保存在内部的数据读取出来作为className值
	$('body').toggleClass();
测试用例2:我们连续调用toggleClass()时候,如果第二次调用的时候传入了false,那么表示是 移除而不是添加,所以刚才保存在内部数据中的class不会被重读!

    $('body').toggleClass();
	//这时候就会把body元素的class全部保存到body的内部数据中,同时把class设置为空字符串!
	var expando=jQuery.expando;
	var key=$('body')[0][expando];
	var walhouse=jQuery.cache;
	var data=walhouse[key];
	//这时候是jQuery._data,所以是内部数据,不会保存在data域下面
	//打印Object {__className__: "body qinliang0"}
	console.log(data);
	//这时候继续调用toggleClass不传入参数,就会把刚才保存在内部的数据读取出来作为className值
	$('body').toggleClass(false);
如果上面第二个参数是true,那么还是可以进行切换的。只要不是明确指定了为false。这就是上面的if判断的情况if ( type === strundefined || type === "boolean" )!

你可能感兴趣的:(jQuery源码分析之addClass,removeClass,toggleClass,hasClass详解四问)