jQuery的屬性操作主要包括
jQuery.fn.val
jQuery.fn.attr
jQuery.fn.removeAttr
jQuery.fn.prop
jQuery.fn.removeProp
jQuery.fn.addClass
jQuery.fn.removeClass
jQuery.fn.toggleClass
接下來一一分析jQuery對他們的處理
a. jQuery.fn.val
jQuery.fn.val用來獲取jQuery對象的第一個元素的val值或者給jQuery對象的每一個元素設置其val值。參數個數為0表示獲取get,否則表示設置set。
處理過程比較簡單:
判斷參數個數,沒有參數表示獲取當前匹配元素中的第一個元素的value值,此時如果能使用valHooks則使用之,否則使用elem.value獲取值(null/undefined需要轉成空字符"");
如果有參數,表示要為當前所有的匹配元素設置值,如果參數是函數,調用函數的返回值作為值val,否則使用傳遞的參數做為值val。能使用則用之,否則使用elem.value = val。
源碼:

val: function( value ) { var ret, hooks, isFunction, elem = this[0];//獲取jQuery對象的第一個元素 //獲取值 if ( !arguments.length ) { if ( elem ) { hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; //通過hooks如果能取到值則返回 if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { return ret; } //否則正常取值 ret = elem.value; return typeof ret === "string" ? // 換行符轉換成空字符 ret.replace(rreturn, "") : //處理null/undef 或數字 ret == null ? "" : ret; } return; } isFunction = jQuery.isFunction( value ); //對jQuery對象的每一個元素設置val return this.each(function( i ) { var val, self = jQuery(this); //元素不為DOM節點則返回 if ( this.nodeType !== 1 ) { return; } if ( isFunction ) { val = value.call( this, i, self.val() ); } else { val = value; } //用空字符替換null/undefined;數字轉化成字符串 if ( val == null ) { val = ""; } else if ( typeof val === "number" ) { val += ""; } else if ( jQuery.isArray( val ) ) { val = jQuery.map(val, function ( value ) { return value == null ? "" : value + ""; }); } hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; //如果hooks的set返回為undefined,則使用正常設置 if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { this.value = val; } }); }
b. jQuery.fn.attr
設置或返回當前jQuery對象所匹配的元素節點的屬性值。
attr: function( name, value ) { return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); }
當前匹配的元素挨個執行jQuery.attr,並返回執行結果集。
$(...).attr的最基礎api函數jQuery.attr。關鍵代碼如下,其他省略。深度理解鈎子機制。
notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); // All attributes are lowercase // Grab necessary hook if one is defined if ( notxml ) { name = name.toLowerCase(); //查找hook hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); } if ( value !== undefined ) { if ( value === null ) { jQuery.removeAttr( elem, name ); //如果有hooks,就使用hooks來處理 } else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; } else {…}
//如果有hooks,就使用hooks來處理 } else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { return ret; } else {…}
c. jQuery.fn.removeAttr
用於移除在當前jQuery對象所匹配的每一個元素節點上指定的屬性
removeAttr: function( name ) { return this.each(function() { jQuery.removeAttr( this, name ); }); }
內部使用低級API jQuery.removeAttr實現
底層使用elem.removeAttribute來實現。不過需要注意的是因為可能根本就沒有要刪除的屬性,所以在刪除之前都設置了該屬性
// Boolean attributes get special treatment (#10870) if ( rboolean.test( name ) ) { // 如果是bool類型的屬性(attribute)設置相應的特性(property) //同時清除defaultChecked/defaultSelected (if appropriate) for IE<8 if ( !getSetAttribute && ruseDefault.test( name ) ) { elem[ jQuery.camelCase( "default-" + name ) ] = elem[ propName ] = false; } else { elem[ propName ] = false; } // See #9699 for explanation of this approach (setting first, then removal) } else { jQuery.attr( elem, name, "" ); }
完整的jQuery.removeAttr源碼如下

removeAttr: function( elem, value ) { var name, propName, i = 0, attrNames = value && value.match( core_rnotwhite ); if ( attrNames && elem.nodeType === 1 ) { while ( (name = attrNames[i++]) ) { propName = jQuery.propFix[ name ] || name; // Boolean attributes get special treatment (#10870) if ( rboolean.test( name ) ) { // 如果是bool類型的屬性(attribute)設置相應的特性(property) //同時清除defaultChecked/defaultSelected (if appropriate) for IE<8 if ( !getSetAttribute && ruseDefault.test( name ) ) { elem[ jQuery.camelCase( "default-" + name ) ] = elem[ propName ] = false; } else { elem[ propName ] = false; } // See #9699 for explanation of this approach (setting first, then removal) } else { jQuery.attr( elem, name, "" ); } elem.removeAttribute( getSetAttribute ? name : propName ); } } }
d. jQuery.fn.prop
在分析這個函數之前先說一點必備知識。
attribute和property的區別
1) property是對象的屬性值(有的時候文章中也稱為特征值,他們是相同的),通過elem[ name ]來取值/賦值; 而attribute是直接寫在標簽上的屬性,通過elem.getAttribute /elem.setAttribute。觀察一張圖很直觀的理解(引用Aaron的圖例)
attributes是一個類數組的容器,說得准確點就是NameNodeMap,總之就是一個類似數組但又和數組不太一樣的容器。attributes的每個數字索引以名值對(name=”value”)的形式存放了一個attribute節點。attributes是會隨着添加或刪除attribute節點動態更新的。property就是一個屬性,如果把DOM元素看成是一個普通的Object對象,那么property就是一個以名值對(name=”value”)的形式存放在Object中的屬性。要添加和刪除property也簡單多了,和普通的對象沒啥分別。之所以attribute和property容易混倄在一起的原因是,很多attribute節點還有一個相對應的property屬性
2) boolen類型的attr值更改是通過prop(elem[ propName ] = false;)方式來處理的,因為properties就是瀏覽器用來記錄當前值的東西,boolean properties保持最新。但相應的boolean attributes是不一樣的,它們僅被瀏覽器用來保存初始值。
jQuery.fn.prop內部使用低級API jQuery.prop實現
prop: function( name, value ) { return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); },
jQuery.prop的核心代碼如下,其他代碼省略
if ( notxml ) { // Fix name and attach hooks name = jQuery.propFix[ name ] || name; //查找hook hooks = jQuery.propHooks[ name ]; } if ( value !== undefined ) { //如果有hooks,就使用hooks來處理 if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; } else { return ( elem[ name ] = value ); } } else { //如果有hooks,就使用hooks來處理 if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { return ret; } else { return elem[ name ]; } }
e. jQuery.fn.removeProp
jQuery.fn.removeProp比較簡單,就如同普通的js對象一樣刪除某個屬性直接使用delete即可
propFix: { tabindex: "tabIndex", readonly: "readOnly", "for": "htmlFor", "class": "className", maxlength: "maxLength", cellspacing: "cellSpacing", cellpadding: "cellPadding", rowspan: "rowSpan", colspan: "colSpan", usemap: "useMap", frameborder: "frameBorder", contenteditable: "contentEditable" }
removeProp: function( name ) { name = jQuery.propFix[ name ] || name; return this.each(function() { //try/catch handles cases where IE balks (such as removing a property on window) try { //刪除prop處理 this[ name ] = undefined; delete this[ name ]; } catch( e ) {} }); }
f. jQuery.fn.addClass
這個函數處理比較簡單,將參數字符串使用空格分隔(多個class)為classes,將原來的ClassName獲取出來為cur,將分classes添加到cur中即可(在此過程中需要保證classes[i]在cur中不存在即可)
重點源碼
//使用空格符分隔參數
classes = ( value || "" ).match( core_rnotwhite ) || []; for ( ; i < len; i++ ) { elem = this[ i ];
//獲取原來的class 名稱 cur = elem.nodeType === 1 && ( elem.className ? ( " " + elem.className + " " ).replace( rclass, " " ) : " " ); if ( cur ) { j = 0;
//class名稱組合 while ( (clazz = classes[j++]) ) {
//保證class名稱的唯一性 if ( cur.indexOf( " " + clazz + " " ) < 0 ) { cur += clazz + " "; } } elem.className = jQuery.trim( cur ); } }
g. jQuery.fn.removeClass
這個函數也比較簡單了,這里不分析了
h. jQuery.fn.toggleClass
這個函數只需要在調用.addClass和.removeClass之間切換即可。toggleClass因為可能需要來回切換的原因,需要保存原來的class,以便下次調用的時候恢復回來。不過需要注意的是當沒有傳遞參數時,被認為是整個Class的切換,需要保存原來的class,以便下次調用的時候恢復回來,關鍵代碼如下
// Toggle whole class name else if ( type === core_strundefined || type === "boolean" ) { if ( this.className ) { jQuery._data( this, "__className__", this.className ); } this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; }
完整源碼

toggleClass: function( value, stateVal ) { var type = typeof value, isBool = typeof stateVal === "boolean"; 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 ), state = stateVal, classNames = value.match( core_rnotwhite ) || []; while ( (className = classNames[ i++ ]) ) { // check each className given, space separated list state = isBool ? state : !self.hasClass( className ); self[ state ? "addClass" : "removeClass" ]( className ); } // Toggle whole class name } else if ( type === core_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__" ) || ""; } }); },