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;
},