jQuery-1.9.1源碼分析系列(八) 屬性操作


  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;
        }
    });
}
View Code

 

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 );
    }
  }
}
View Code

  

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__" ) || "";
                }
            });
        },
View Code

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM