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)、<div chaojidan="hello" id="div1">

$("#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 type="checkbox" checked="checked">, $("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(<select multiple><option></option></select>,多選)。

	        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屬性值之間用空白符隔開,而不是空格隔開的。
//比如:<div class="box1  box2">,這裏的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;
  },
發佈了41 篇原創文章 · 獲贊 15 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章