jQuery源码学习(11)-属性方法解析和钩子hook

1、jQuery提供了一些快捷函数来对dom对象的属性进行存取操作。

实例方法有:

jQuery.fn.extend({
    attr
    removeAttr
    prop
    removeprop
    addClass
    removeClass
    toggleClass
    hasClass
    val
});

静态方法有:

jQuery.extend({
    valHooks
    attr
    removeAttr
    attrHooks
    propFix
    prop
    propHooks
});

静态方法是内部使用的,特别是供实例方法调用,实例方法才是对外的。

这部分方法一般都有两个特点:

  • set方法和get方法一体化. 根据参数数量来判断是set还是get.
  • value可以传入一个闭包. 这个闭包的返回值才是真正的value。

举例:

$("#div1").attr("title","hello") ,设置属性,两个参数时。

$("#div1").attr("title") , 获取属性值,一个参数时。

$("#div1").prop("title"),也可以获得这个属性值。

着重说明一下attr和prop的区别

attribute:特性

  • 直接写在标签上的属性,可以通过setAttribute、getAttribute、removeAttribute进行设置、读取、删除属性

property:属性

  • 通过“.”号来进行设置、读取的属性,就跟Javascript里普通对象属性的读取差不多

举例:

(1)、

$("#div1").attr("chaojidan","hello") ,给元素添加属性名为chaojidan的属性。在元素div标签上会显示chaojidan这个属性。

$("#div1").prop("chaojidan","hello"),也是给元素添加属性名为chaojidan的属性,但是在元素div标签上不会显示这个属性。

因为chaojidan是自定义属性,不是元素的固有属性。基本可以总结为attribute节点都是在HTML代码中可见的,而property只是一个普通的名值对属性

(2)、

$("#div1").attr("chaojidan") 返回hello。但是$("#div1").prop("chaojidan"),在有些浏览器下会返回空。因为chaojidan是自定属性。

(3)、对于a标签的href属性,attr返回href的属性值,但是prop返回document.URL + href的属性值。href是a标签的固有属性。

2、源码分析

jQuery.fn.extend({
  attr: function( name, value ) {
	//此方法之前讲过,如果arguments.length > 1,就代表是设置操作,如果是false,那就代表是获取操作。
	//而真正调用的回调方法是静态方法:jQuery.attr。name就是你传进来的属性名,value是你传进来的属性值。
    return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); 
  },

  removeAttr: function( name ) {
    return this.each(function() {
	        //实例方法removeAttr,调用的也是同名的静态方法removeAttr。
      jQuery.removeAttr( this, name );     
    });
  },

  prop: function( name, value ) {   //也是静态方法jQuery.prop
    return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
  },

  removeProp: function( name ) {
    return this.each(function() {
			//删除属性,如果属性名需要做兼容,就做兼容,比如:class要变成className。
      delete this[ jQuery.propFix[ name ] || name ];   
    });
  },

attr方法和prop方法均调用了jQuery.access函数,jQuery.access主要作用是修正参数.access函数里的第二个参数jQuery.attr. 这个参数的作用是告诉access方法, 修正完参数后再去调用 jQuery.attr方法.access方法是可以被抽象出复用的一组对参数的修正方法,通过分解成单一的数据后,然后调用传递的回调处理钩子 比如 attr,css, prop.等等。

access源码:

//将对象传参分解成单一的参数从而set和get处理
access: function( elems, fn, key, value, chainable, emptyGet, raw ) {
		var i = 0,
			length = elems.length,
			bulk = key == null;

		// Sets many values
		if ( jQuery.type( key ) === "object" ) {  //传递是对象
			chainable = true;
			for ( i in key ) {    //递归调用
				jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
			}

		// Sets one value
		} else if ( value !== undefined ) {
			chainable = true;

			if ( !jQuery.isFunction( value ) ) {
				raw = true;
			}

			if ( bulk ) {
				// Bulk operations run against the entire set
				if ( raw ) {
					fn.call( elems, value );
					fn = null;

				// ...except when executing function values
				} else {
					bulk = fn;
					fn = function( elem, key, value ) {
						return bulk.call( jQuery( elem ), value );
					};
				}
			}

			if ( fn ) {
				for ( ; i < length; i++ ) {
					fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
				}
			}
		}

		return chainable ?
			elems :

			// Gets
			bulk ?
				fn.call( elems ) :
				length ? fn( elems[0], key ) : emptyGet;
	},

3、一些核心jQuery函数都有自己的“插件API”称为“钩子”

    "钩子"是jQuery提供的API来调用用户自定义的函数,用于扩展,以便获取和设置特定属性的值。钩子机制是jQuery用来处理浏览器兼容的手法。钩子在.attr(), .prop(), .val() and .css() 四种操作中会涉及。

3.1 钩子机制:(以属性Attribute钩子举例)

IE9-浏览器中,将input标签更改类型(type)为radio类型以后,value属性可能出现异常。所以我们定义了一个属性钩子(attrHooks)中类型(type)在更改设置(set)的一个处理。结构如下:

//属性钩子对象(所有的属性钩子都放在里面)
attrHooks: {
  //属性为type的钩子
  type: {
    //操作为set的钩子
    set: function( elem, value ) {
      if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
        //IE6-9设置完type后恢复value属性(attr)
        var val = elem.value;
        elem.setAttribute( "type", value );
        if ( val ) { elem.value = val; }
          return value;
        }
      }
    }
  }
}

由上可知钩子结构为:钩子对象:{钩子类型:{钩子操作:xxx},……}(有的钩子类型可省略)

参考博客:https://blog.csdn.net/chen_hua89/article/details/50824529

3.2 钩子作用

在做css3属性浏览器兼容的时候,都需要特定的前缀:

Webkit的浏览器:-webkit-border-radius

Firefox:-moz-border-radius

此时我看可以采用一个CSS hook 可以标准化这些供应商前缀的属性,让.css() 接受一个单一的,标准的属性的名称(border-radius,或用DOM属性的语法,borderRadius),判断的代码省略,直接看实现:

给某一元素设置borderRadius,为10px

$("#element").css("borderRadius", "10px");

为了做浏览器兼容,我们不得不

if(webkit){
   ........................
}else if(firefox){
  ............................
}else if(...)更多

这是一种最没技术含量的写法了,如果我们换成一种hook的话

复制代码
$.cssHooks.borderRadius = {
      get: function( elem, computed, extra ) {
        return $.css( elem, borderRadius );
      },
      set: function( elem, value) {
        elem.style[ borderRadius ] = value;
      }
    };
复制代码

borderRadius = styleSupport( "borderRadius" ); //获取到相对应的浏览器标准

3.3 钩子在attr和prop中的应用

jQuery.extend中涉及静态函数的源码:

attr: function( elem, name, value ) {
    var hooks, ret,
      nType = elem.nodeType;
	//元素不存在或文本节点,或注释节点,或属性节点,不能设置属性,
	//直接返回。元素节点才能设置属性。
    if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {  
      return;
    }
	//core_strundefined = "undefined"。document,window没有getAttribute方法,
	//因此使用prop方法,而prop方法是用.属性名的形式。
    if ( typeof elem.getAttribute === core_strundefined ) {  
      return jQuery.prop( elem, name, value );
    }
	//如果不是元素节点或元素节点是不是xml文档下的,如果是,那么jQuery.isXMLDoc( elem ) 返回true。
	//这里的意思就是:如果是xml文档下的元素节点,就不会进入到if语句。xml文档下的元素都是自定义的,没有兼容性问题。
	//所以不需要进入到if语句,进行兼容性处理。而html文档下的元素节点,有兼容性问题,所以需要做下处理。
    if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {  

      name = name.toLowerCase();

      //hooks是jQuery中专门用来解决兼容性问题的。support用来检测浏览器的兼容性,hooks来解决兼容性问题,
		//hooks针对不同的类型有相对应的hooks,比如:attr,就对应于attrHooks。
		//hooks分两种,一种是针对设置的兼容性处理,set方法,一种是针对获取的兼容性处理,get方法。
		//如果有兼容性问题,set方法或get方法会返回兼容性处理之后的值,如果没有兼容性问题,set就会返回undefined,get就会返回null。
		//大家可以看下attrHooks对象,其实传属性名进来,只有type属性才有兼容性问题。而且只针对设置操作,获取操作没有兼容性问题。
		//具体一点就是:设置type = "radio" 的兼容性问题。

      hooks = jQuery.attrHooks[ name ] || ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );  
/*jQuery.expr = Sizzle.selectors,Sizzle.selectors对象中有match: matchExpr属性。
matchExpr也是一个对象,它里面的bool属性值是:new RegExp( "^(?:" + booleans + ")$", "i" )。
而booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped"。
nodeHook = undefined。如果不是在IE下设置type类型为radio,那么就会判断name是否匹配此正则。
如果匹配,就返回boolHook,而不匹配就会返回undefined。boolHook是用来专门处理bool类型属性的。
比如:, $("input").attr("checked") : checked,$("input").prop("checked") : true。
checked属性就属于bool类型属性。针对以上这个例子,我们知道attr获取checked的值是checked,
因为当我们设置是也应该$("input").attr("checked","checked"),但有些人可能对jQuery不熟,会写成$("input").attr("checked",true),
那么这种写法行不行呢,也是可以的,因为jQueyr里面做了兼容处理。其实就是boolHook对象,大家可以在文章的最后看到这个对象,看它是如何处理的。*/

    }

    if ( value !== undefined ) {   //设置操作

      if ( value === null ) {  //$("#div1").attr("chaojidan",null)这种情况,会把chaojidan的属性移除。
        jQuery.removeAttr( elem, name );

      } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
        return ret;   //如果有兼容性问题,就进行处理,然后把处理的值返回。

      } else {
			//用普通的方式,进行设置操作。因为属性值都是字符串,所以把number转化成字符串。
        elem.setAttribute( name, value + "" );   
        return value;
      }

    } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { 
      return ret;   //获取操作。先看是否有get的兼容性操作。

    } else {
		//jQuery.find = Sizzle。Sizzle中的attr方法对getAttribute 方法进行了兼容性处理。
      ret = jQuery.find.attr( elem, name );  

      return ret == null ? undefined : ret;
    }
  },

removeAttr: function( elem, value ) {
    var name, propName,
      i = 0,
//core_rnotwhite = /\S+/g,这里的意思就是可以同时删除多个属性值,
//比如:$("div").removeAttr("id name class");,value = "id name class",
//调用match方法,并传入正则/\S+/g,会返回[id,name,class]。
        attrNames = value && value.match( core_rnotwhite );  

    if ( attrNames && elem.nodeType === 1 ) {   //必须是元素节点Element
      while ( (name = attrNames[i++]) ) {
//propFix: {"for": "htmlFor","class":"className"},如果要删除的属性名是for或者class,那么需要做兼容处理。
//因此你做$("div").removeAttr("class")操作时,就不会出问题。
        propName = jQuery.propFix[ name ] || name;  
		//如果要删除的属性名属于bool型的属性(也就是说它的值通过[属性名]获取时,是false或者true)
        if ( jQuery.expr.match.bool.test( name ) ) {  
//需要把此bool型的属性值赋为false,因为这个属性已经被移除了,不应该用[属性名]获取时,返回true。
//比如:input元素的checked属性,当你移除这个checked属性时,你通过input.checked获得true,
//那么就会被认为input中有这个checked,而这时checked你已经移除了,所以必须设置它的input.checked=false。
          elem[ propName ] = false;   

        }

        elem.removeAttribute( name );   //调用原生的方法移除掉
      }
    }
  },

attrHooks: {
    type: {
//这个方法是解决这样一个问题的:input = document.createElement("input");input.value = "t";
//input.type = "radio";support.radioValue = input.value === "t";
//当你先对input的value赋值,然后再设置input的type为radio时,IE下的input的value会变成on,而其他浏览器会得到t。
      set: function( elem, value ) {    
//如果存在以上这个兼容性问题,也就是jQuery.support.radioValue =false,IE下是false,value就是你设置type属性的值,并且元素是input,
//意思就是:你对input元素设置type=radio的操作。
        if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {  
//怎么解决IE下的这个兼容性问题呢,我们先把这个input的value值保存起来。等设置了type = "radio" 后,再把值赋过去。
//这样它的input的value就不会变成on了。
          var val = elem.value;    
          elem.setAttribute( "type", value );
          if ( val ) {
            elem.value = val;
          }
          return value;
        }
      }
    }

propFix: {

"for": "htmlFor", //htmlFor用于读取label标签的for属性

"class": "className" }, prop: function( elem, name, value ) {     var ret, hooks, notxml,       nType = elem.nodeType;     if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {       return;     }     notxml = nType !== 1 || !jQuery.isXMLDoc( elem );     if ( notxml ) { //不是xml文档,需要做兼容处理       name = jQuery.propFix[ name ] || name; //propFix上面已经讲了 //如果name是tabIndex,需要做兼容处理。tabIndex可以切换光标的顺序(通过tab键), //按元素中tabIndex的属性值大小(1,2,3....),从小到大进行切换。       hooks = jQuery.propHooks[ name ];     }     if ( value !== undefined ) { //设置操作 //这里如果返回的是elem[ name ] = value,其实是return value。       return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ? ret : ( elem[ name ] = value );     } else { //获取操作       return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ? ret : elem[ name ];     }   },  propHooks: { //此属性是在获取时,会有兼容性问题。其实就是在元素默认不支持tabIndex属性时, //并且没有显式设置它的tabIndex属性时,IE6-8会返回0,而标准浏览器下会返回-1。所以兼容处理,都返回-1.     tabIndex: {       get: function( elem ) { //rfocusable = /^(?:input|select|textarea|button)$/i;,如果元素不属于正则中指定的这些元素时, //并且元素没有href属性,那么就证明此元素默认不支持tabIndex属性。         return elem.hasAttribute( "tabindex" ) || rfocusable.test( elem.nodeName ) || elem.href ? elem.tabIndex : -1;       }     }   } });
if ( !jQuery.support.optSelected ) {   //这里的hooks是针对select元素的第一个option元素是否会默认被选中。
//在IE下(老版本safari),不会默认选中,因此获取option的selected值时返回false, 而其他浏览器返回true。
  jQuery.propHooks.selected = {  
//只有get方法,因为只有获取时才会出现这个问题。假设你要获取option的selected属性值。
    get: function( elem ) {    
      var parent = elem.parentNode;   
      if ( parent && parent.parentNode ) {
/*只要在获取option的selected的值时,先访问select.selectedIndex属性,
就可以设置option.selected = true了。意思就是在访问option的selected属性时,
先访问其父级select元素的selectedIndex属性,强迫浏览器计算option的selected属性,
以得到正确的值。需要注意的是option元素的父元素不一定是select,也有可能是optgroup。这里是支持IE9+,
所以option的parentNode是optgroup,optgroup的parentNode是select。*/
        parent.parentNode.selectedIndex;  
      }
      return null;
    }
  };
}

jQuery.each([     //不懂each方法的,可以看看前面博客关于静态方法each的解析
  "tabIndex",
  "readOnly",
  "maxLength",
  "cellSpacing",
  "cellPadding",
  "rowSpan",
  "colSpan",
  "useMap",
  "frameBorder",
  "contentEditable"
  ], function() {
	//这里的this就是数组中的选项,比如:jQuery.propFix[ tabIndex.toLowerCase() ] = tabIndex;之所以这样做,是以防有人做jQuery属性操作时,
	//把名字写成了全部是小写的情况,这里做下兼容,使用户输入全部是小写属性名,也能正常操作。
    jQuery.propFix[ this.toLowerCase() ] = this;    
});

3.4 关于val方法和对其兼容的处理钩子函数valHook

val方法的使用:

$("#input1").val()      //获取input元素的value属性值

$("#input1").val("hello")      //设置input元素的value属性值为hello。

在对一个元素调用 .val() 函数时,首先取其 value 属性的值,如果没有的话,再使用其 text 值。

实例方法源码:
val: function( value ) {
	    var hooks, ret, isFunction,
	      elem = this[0];

	    if ( !arguments.length ) {   //如果是获取操作,也就是参数为0时.
	      if ( elem ) {
	        hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];

	        //valHooks有以下几个属性对象:option(下拉框的子选项),select(下拉框),radio(单选按钮),checkbox(复选按钮)。
	//也就意味着需要对这四种元素进行兼容性处理。其中radio的type=radio,checkbox的type=checkbox,select的type,默认为select-one(单选),
	//还可以设置成select-multiple(,多选)。

	        if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
	          return ret;
	        }

	        ret = elem.value;

	        return typeof ret === "string" ? ret.replace(rreturn, "") : ret == null ? "" : ret;
	      }

	      return;
	    }

	    isFunction = jQuery.isFunction( value );

	    return this.each(function( i ) {  //设置操作,是针对每个元素
	      var val;

	      if ( this.nodeType !== 1 ) {  //必须是元素节点
	        return;
	      }

	      if ( isFunction ) {
	        val = value.call( this, i, jQuery( this ).val() );
	      } else {
	        val = value;
	      }

	      if ( val == null ) {  //针对这种情况:$("input").val(null);
	        val = "";
	      } else if ( typeof val === "number" ) {   //如果传入的是数字类型,就转换成字符串
	        val += "";
	      } else if ( jQuery.isArray( val ) ) {   
	//这里是针对checkbox,radio这种元素的,比如:$("#input2").val(["hello"]);这里如果传入的是字符串的话,是对checkbox的value属性赋值,
	//但是传入数组,就代表checkbox的value是否等于hello,如果等于,就被选择上,如果不等于就不被选择上。
	        val = jQuery.map(val, function ( value ) {
	          return value == null ? "" : value + "";
	        });
	      }

	      hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];

	      if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {  //请看下面的代码解释
	        this.value = val;
	      }
	    });
	  }

静态方法源码:

valHooks: {
	    option: {  
	/*当你获取option元素的value属性值时,如果没有对此option显式设置value值,获取到的值是option的text,也就是option的文本。
	但是IE6-7下获取到的值是""。*/
	      get: function( elem ) {
	        var val = elem.attributes.value;   //在IE6-7下,val是一个object。
	        return val.specified ? elem.value : elem.text;
	//如果val.specified为true,就代表value被显式设置了,因此直接返回elem.value,如果为false,就代表没有显式设置,因此返回elem.text。
	      }
	    },
	    select: {
	      get: function( elem ) {    
	//当select是单选时,获取的value值,就是你选择的那个option的值,如果是多选,获取值时,就是你选择的所有option的值的数组形式。
	        var value, option,
	          options = elem.options,   //select的所有option的集合。
	            index = elem.selectedIndex,  //当前选择的option的索引值
	              one = elem.type === "select-one" || index < 0,
	                values = one ? null : [],   //如果是单选,values=null,如果是多选,values=[]。
	                  max = one ? index + 1 : options.length,
	                    i = index < 0 ? max : one ? index : 0;


	        for ( ; i < max; i++ ) {   //单选,循环一次,多选,循环多次
	          option = options[ i ];

	          if ( ( option.selected || i === index ) &&   
	//IE6-9下,点击reset按钮时,option的selected不会恢复默认值,其他浏览器会恢复所有option的selected的默认值。

	            ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&     
	//如果option被设置了disabled,那么获取option的值时,是获取不到的。
	              ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {     
	//如果option的父元素被设置了disabled,并且父元素是optgroup,那么也获取不到。

	            value = jQuery( option ).val();

	            if ( one ) {
	              return value;
	            }

	            values.push( value );
	          }
	        }

	        return values;
	      },

	      set: function( elem, value ) {
	        var optionSet, option,
	          options = elem.options,
	            values = jQuery.makeArray( value ),   //把value转换成数组
	              i = options.length;

	        while ( i-- ) {
	          option = options[ i ];
	          if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {  
	//判断select的子元素option的value是否在values数组中,如果在,就会把这个option选中。
	            optionSet = true;
	          }
	        }

	        if ( !optionSet ) {
	          elem.selectedIndex = -1;  
	//如果select下的option的value值没有一个等于value的,那么就让select的选择索引值赋为-1.让select框中没有任何值。
	        }
	        return values;
	      }
	    }
	  }
// Radios and checkboxes getter/setter
jQuery.each([ "radio", "checkbox" ], function() {
  jQuery.valHooks[ this ] = {
    set: function( elem, value ) {
      if ( jQuery.isArray( value ) ) {    
//当value是数组时,看此元素的value值是否在数组value中,如果在就让元素被选择上。此元素只有radio,checkbox这两种。

        return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
      }
    }
  };
  if ( !jQuery.support.checkOn ) {   
//如果元素是radio或者checkbox,我们去获取它的默认value值时,老版本webkit得到的值是"",
//而其他浏览器是on,因此当没有对此元素显式设置它的value值时(通过getAttribute获取的value的是null),
//我们通过input.value获取它的默认值,所有浏览器都返回on。
    jQuery.valHooks[ this ].get = function( elem ) {
      return elem.getAttribute("value") === null ? "on" : elem.value;
    };
  }
});

3.5 实例属性方法addClass、toggleClass、hasClass函数解析

方法的使用:

$("#div1").addClass("box1 box2");     //给元素div的class属性添加box1和box2

$("#div1").removeClass("box1");     //删除元素div的class属性值box1

$("#div1").toggleClass("box1");     //如果元素div的class属性值中有box1,那么就删除box1。如果没有,那么就添加box1.

$("#div1").hasClass("box1");   //元素div的class属性值是否有box1,如果有,就返回true,如果没有,就返回false。

源码:

// 为匹配的每个元素增加指定的class(es)
addClass: function( value ) {
    var classes, elem, cur, clazz, j,
      i = 0,
        len = this.length,     //this指的是$("div")
          proceed = typeof value === "string" && value; 
//判断传入的参数是否是字符串。我们在例子中,传入的都是字符串的形式,其实此方法,还可以传入回调方法,
//比如:$("div").addClass(function(index){  return "box"+index; })  ,回调方法的返回值,将会作为addClass的参数传入。
//这段代码就会在第一个div的class属性中添加box0,在第二个div的class属性中添加box1,以此类推。

    if ( jQuery.isFunction( value ) ) {   //传入的参数是否是函数
      return this.each(function( j ) {
        jQuery( this ).addClass( value.call( this, j, this.className ) );  
//回调方法的第一个参数是当前元素的index值,第二个参数是当前元素的class属性值。
      });
    }

    if ( proceed ) {   //如果是字符串
      classes = ( value || "" ).match( core_rnotwhite ) || [];  
	// core_rnotwhite = /\S+/g,把"box1 box2"转换成[box1,box2]

      for ( ; i < len; i++ ) {  //循环元素
        elem = this[ i ];
        cur = elem.nodeType === 1 && ( elem.className ? ( " " + elem.className + " " ).replace( rclass, " " ) :" ");   
//如果是元素节点,就继续进行判断元素的class属性值是否存在,rclass = /[\t\r\n\f]/g,\t是制表符,\r是回车,\n换行符,\f是换页。
//这些都是空白符,不是空格,我们需要把空白符替换成空格,以防元素的class属性值之间用空白符隔开,而不是空格隔开的。
//比如:
,这里的box1和box2之间的\t就会替换成" "。         if ( cur ) { // " ",为真,""为假。           j = 0;           while ( (clazz = classes[j++]) ) {             if ( cur.indexOf( " " + clazz + " " ) < 0 ) { //判断元素之前是否有此class属性值,没有才添加               cur += clazz + " ";             }           }           elem.className = jQuery.trim( cur ); //最后,去掉前后空格。         }       }     }     return this;   },   removeClass: function( value ) {     var classes, elem, cur, clazz, j,       i = 0,         len = this.length,           proceed = arguments.length === 0 || typeof value === "string" && value; //&&优先级高于||,所以先执行后面的&&操作。当不传入什么参数时,将会删除此元素class所有的属性值。 //比如:$("#div1").removeClass(),div1元素的class属性值将会变成""。     if ( jQuery.isFunction( value ) ) { //如果传入的是回调方法       return this.each(function( j ) {         jQuery( this ).removeClass( value.call( this, j, this.className ) );       });     }     if ( proceed ) { //如果传入的是字符串或者什么都没传       classes = ( value || "" ).match( core_rnotwhite ) || [];       for ( ; i < len; i++ ) {         elem = this[ i ];         cur = elem.nodeType === 1 && ( elem.className ?( " " + elem.className + " " ).replace( rclass, " " ) :"");         if ( cur ) { //如果此元素有class属性值,就进入if语句。           j = 0;           while ( (clazz = classes[j++]) ) {             while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { //如果存在,就把此值删除               cur = cur.replace( " " + clazz + " ", " " );             }           }           elem.className = value ? jQuery.trim( cur ) : ""; //如果没传入参数,就把元素的class属性值赋为""。         }       }     }     return this; //链式操作   },   toggleClass: function( value, stateVal ) { //第二个参数,如果为true,就代表addClass,如果为false,就代表removeClass。     var type = typeof value;     if ( typeof stateVal === "boolean" && type === "string" ) { //$("div").toggleClass("box1 box2",true);       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" ) {         var className,           i = 0,             self = jQuery( this ),               classNames = value.match( core_rnotwhite ) || [];         while ( (className = classNames[ i++ ]) ) {           if ( self.hasClass( className ) ) { //元素如果有此class属性值,就删除             self.removeClass( className );           } else {             self.addClass( className );           }         }       } else if ( type === core_strundefined || type === "boolean" ) { //core_strundefined = undefined,如果是这种操作,$("#div1").toggleClass(false); //或者$("#div1").toggleClass();就会进入else if语句。         if ( this.className ) { //如果此元素有class属性值,就把属性值存入jQuery缓存系统中。           data_priv.set( this, "__className__", this.className );         }         this.className = this.className || value === false ? "" : data_priv.get( this, "__className__" ) || ""; //假设div1有class="box1 box2",那么执行$("#div1").toggleClass(false);或者$("#div1").toggleClass();将会把div1的class=""。之后, //你再调用$("#div1").toggleClass(true);或者$("#div1").toggleClass();又会把dv1的class="box1 box2"。       }     });   },   hasClass: function( selector ) {     var className = " " + selector + " ",       i = 0,         l = this.length;     for ( ; i < l; i++ ) { //对所有匹配元素进行class的操作,也就是说$("div"),hasClass("box"),只要页面上的任何一个div的class属性值有box,就会返回true。       if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {         return true;       }     }     return false;   },

你可能感兴趣的:(jQuery源码分析)