JQuery源码解读__7__属性操作方法

207 阅读6分钟

JQ的属性操作方法( 妙味讲堂 - 视频笔记)

1、attr、prop使用对比

1、对比

attr:

通过setAttribute和getAttribute进行设置和获取属性值
prop:
通过obj.prop/obj[prop]来设置和获取属性值,所以自定义属性值是没法操作的
2、实例  

设置自带属性都attr、prop都可以实现

设置和获取自定义属性只有attr,prop是无法设置和获取自定义属性的

<div id = 'box'></div>
$("#box").attr("title","hello"); // 设置title属性为hello
$("#box").attr("title"); // 获取title属性值
$("#box").prop("title","hello"); // 设置title属性为hello
$("#box").prop("title"); // 获取title属性值
let box = document.getElementById("box");
box.setAttribute("title","hello");// 设置title属性为hello
box.title = 'hello';// 设置title属性为hello
box['title'] = 'hello';// 设置title属性为hello
box.getAttribute("title"); // 获取title属性值
box.title; // 获取title属性值box['title']; // 获取title属性值

2、addClass、removeClass、toggleClass使用与对比

//1、添加还没有的class,已有的就不用添加
<div id = 'box' class = 'box1'></div>
$("#box").addClass("box2 box3");
<div id = 'box1 box2 box3'></div>

//2、可以传入函数
<div id = 'box' class = 'box1'></div>
$("#box").addClass(function(index){   
  console.log(index);//0只有一个元素   
 return 'box'+index; // 给多个元素添加有规律的class   
});
<div id = 'box1 box0'></div>

// 3、链式调用
<div id = 'box' class = 'box1'></div>
$("box").addClass("box2").addClass("box3");
<div id = 'box' class = 'box1 box2 box3'></div>
// $("box").addClass("box1")执行完会返回一个对象,所以可以执行下一个操作

// 4、移除有的class
<div id = 'box' class = 'box1 box2 box3 box4'></div>
$("#box").removeClass("box3 box4");
<div id = 'box1 box2'></div>

// 5、可以传入函数
<div id = 'box' class = 'box1 box2 box3'></div>
$("#box").removeClass(function(index){  
  console.log(index);//0只有一个元素  
   return 'box'+(index+1); // 给多个元素添加有规律的class 
});
<div id = 'box2 box3'></div>

//6、toggleClass 有就删掉,没有就添加
<div id = 'box1 box2'></div>
$("#box").toggleClass("box3 box4");
<div id = 'box1 box2 box3 box4'></div>

//7、toggleClass 可以传入参数,对元素数组进行批处理
<div id = 'box' class = 'box1 box2 box3'></div>
$("#box").toggleClass(function(index){ 
   console.log(index);//0只有一个元素 
  return 'box'+(index+1);// 给多个元素切换有规律的class  
});
<div id = 'box2 box3'></div>

3、源码实现

1、实例方法
jQuery.fn.extend({  
  attr: function( name, value ) {    
    return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );  
  }, 
   removeAttr: function( name ) {  
      return this.each(function() {       
     jQuery.removeAttr( this, name );   
     });
    },  
  prop: function( name, value ) {   
     return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );   
 },   
 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 {        
        this[ name ] = undefined;     
           delete this[ name ];   
         } catch( e ) {}   
     }); 
   },  
  addClass: function( value ) { 
       var classes, elem, cur, clazz, j,   
         i = 0,            // this 元素对象,多个标签可以同时处理    
        len = this.length,    
        proceed = typeof value === "string" && value;        // 当传入参数是回调函数的时候  
      if ( jQuery.isFunction( value ) ) {      
      // 给元素数组分别添加return设置的class     
       return this.each(function( j ) {   
             jQuery( this ).addClass( value.call( this, j, this.className ) ); 
           });      
  }      
  if ( proceed ) { 
           // The disjunction here is for better compressibility (see removeClass)  
          // 讲传入的参数进行以空白字符为分割点进行分割      
      //  core_rnotwhite = /\S+/g, 匹配所有空格    
        classes = ( value || "" ).match( core_rnotwhite ) || [];   
         for ( ; i < len; i++ ) {            
    elem = this[ i ];                
cur = elem.nodeType === 1 && ( elem.className ?      
              //  rclass = /[\t\r\n\f]/g  空白字符,不止是空格    
                ( " " + elem.className + " " ).replace( rclass, " " ) :
                    " "
                );
                //  cur 经过处理没有空白字符
                if ( cur ) {
                    j = 0;
                    while ( (clazz = classes[j++]) ) {
                        // 不存在要添加的class
                        if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
                            cur += clazz + " ";         
                        }     
             }                    // trim去掉字符串两边的空白字符         
           elem.className = jQuery.trim( cur );       
        }       
     }     
   }        // 链式调用       
 // $("box").addClass("box1").addClass("box2");   
     // $("box").addClass("box1")执行完会返回一个对象,所以可以执行下一个操作     
   // return this就是返回这么个对象,为了实现链式调用   
     return this;   
 },    removeClass: function( value ) {  
      var classes, elem, cur, clazz, j,   
         i = 0,    
        len = this.length,   
         proceed = arguments.length === 0 || typeof value === "string" && value;
        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 ];       
         // This expression is here for better compressibility (see addClass) 
               cur = elem.nodeType === 1 && ( elem.className ?  
                  ( " " + elem.className + " " ).replace( rclass, " " ) :  "" );    
            if ( cur ) {     
               j = 0;         
           while ( (clazz = classes[j++]) ) {        
                // Remove *all* instances   
                     while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {  
                          cur = cur.replace( " " + clazz + " ", " " );    
                    }      
              }       
             elem.className = value ? jQuery.trim( cur ) : "";      
          }      
      }     
   }      
  return this; 
   },    toggleClass: function( value, stateVal ) {  
      var type = typeof value;  
      if ( typeof stateVal === "boolean" && type === "string" ) {      
      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" ) {    
            // toggle individual class names     
           var className,       
             i = 0,            
        self = jQuery( this ),          
          classNames = value.match( core_rnotwhite ) || [];    
            while ( (className = classNames[ i++ ]) ) {       
             // check each className given, space separated list      
              if ( self.hasClass( className ) ) {   
                     self.removeClass( className );       
             } else {           
             self.addClass( 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__" ) || "";      
      }    
    });  
  },  
  hasClass: function( selector ) {  
      var className = " " + selector + " ",     
       i = 0,       
     l = this.length;  
      for ( ; i < l; i++ ) {         
   if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {      
          return true;   
         }  
      }      
  return false;  
  },  
  val: function( value ) {  
      var ret, hooks, isFunction,   
         elem = this[0];     
   if ( !arguments.length ) {       
     if ( elem ) {         
       hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];        
        if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {    
                return ret;                }            
    ret = elem.value;                return typeof ret === "string" ?        
            // handle most common string cases        
            ret.replace(rreturn, "") :        
            // handle cases where value is null/undef or number      
              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;     
       }         
   // Treat null/undefined as ""; convert numbers to string      
      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() ];
            // If set returns undefined, fall back to normal setting
            if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
                this.value = val;
            }
        });
    }});
2、工具方法

jQuery.extend({
    valHooks: {
        option: {
            get: function( elem ) {
                // Use proper attribute retrieval(#6932, #12072)
                var val = jQuery.find.attr( elem, "value" );
                return val != null ?
                    val :
                    elem.text;
            }
        },
        select: {
            get: function( elem ) {
                var value, option, 
                   options = elem.options,
                    index = elem.selectedIndex,
                    one = elem.type === "select-one" || index < 0,
                    values = one ? null : [],
                    max = one ? index + 1 : options.length,
                    i = index < 0 ?
                        max :
                        one ? index : 0;
                // Loop through all the selected options
                for ( ; i < max; i++ ) {
                    option = options[ i ]; 
                   // oldIE doesn't update selected after form reset (#2551)
                    if ( ( option.selected || i === index ) &&
                            // Don't return options that are disabled or in a disabled optgroup
                            ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && 
                           ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
                        // Get the specific value for the option
                        value = jQuery( option ).val();
                        // We don't need an array for one selects
                        if ( one ) {
                            return value;
                        }
                        // Multi-Selects return an array
                        values.push( value );
                    }
                } 
               return values;
            }, 
           set: function( elem, value ) {
                var optionSet, option,
                    options = elem.options,
                    values = jQuery.makeArray( value ),
                    i = options.length;
                while ( i-- ) {
                    option = options[ i ];
                    if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {
                        optionSet = true;
                    }
                } 
               // force browsers to behave consistently when non-matching value is set
                if ( !optionSet ) {
                    elem.selectedIndex = -1; 
               } 
               return values;
            } 
       }
    },    
attr: function( elem, name, value ) {
        var hooks, ret,
            nType = elem.nodeType;
        // don't get/set attributes on text, comment and attribute nodes
        if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
            return;
        }
        // Fallback to prop when attributes are not supported
        if ( typeof elem.getAttribute === core_strundefined ) {
            return jQuery.prop( elem, name, value );
        }
        // All attributes are lowercase
        // Grab necessary hook if one is defined
        if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
            name = name.toLowerCase();
            hooks = jQuery.attrHooks[ name ] ||
                ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
        } 
       if ( value !== undefined ) {
            if ( value === null ) {
                jQuery.removeAttr( elem, name );
            } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
                return ret;
            } else {
                elem.setAttribute( name, value + "" );
                return value;
            }
        } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
            return ret;
        } else {
            ret = jQuery.find.attr( elem, name );
            // Non-existent attributes return null, we normalize to undefined
            return ret == null ?
                undefined :
                ret;
        }
    },
    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 ( jQuery.expr.match.bool.test( name ) ) {
                    // Set corresponding property to false
                    if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
                        elem[ propName ] = false; 
                   // Support: IE<9
                    // Also clear defaultChecked/defaultSelected (if appropriate)
                    } else {
                        elem[ jQuery.camelCase( "default-" + name ) ] =
                            elem[ propName ] = false;
                    }
                // See #9699 for explanation of this approach (setting first, then removal) 
               } else {
                    jQuery.attr( elem, name, "" );
                }
                elem.removeAttribute( getSetAttribute ? name : propName );
            }
        }
    },
    attrHooks: {
        type: {
            set: function( elem, value ) {
                if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
                    // Setting the type on a radio button after the value resets the value in IE6-9
                    // Reset value to default in case type is set after value during creation
                    var val = elem.value;                    elem.setAttribute( "type", value );
                    if ( val ) { 
                       elem.value = val;
                    } 
                   return value;
                } 
           }
        }
    },
    propFix: { 
       "for": "htmlFor",
        "class": "className"
    },
    prop: function( elem, name, value ) {
        var ret, hooks, notxml,
            nType = elem.nodeType;
        // don't get/set properties on text, comment and attribute nodes
        if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { 
           return;
        }
        notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
        if ( notxml ) {
            // Fix name and attach hooks 
           name = jQuery.propFix[ name ] || name;
            hooks = jQuery.propHooks[ name ];
        }
        if ( value !== undefined ) {
            // 兼容处理
            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: {
        // 光标切换,IE某些不是表单组件也可能获取光标,判断是否是可以获取光标元素
        tabIndex: {
            get: function( elem ) {
                var tabindex = jQuery.find.attr( elem, "tabindex" );
                // rfocusable = /^(?:input|select|textarea|button|object)$/i, 
               // 判断标签是否是表单元素
                return tabindex ?
                    parseInt( tabindex, 10 ) :
                    rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? 
                       0 : 
                       -1; 
           }
        }
    }});