jQuery-1.9.1源码分析系列(八) 属性操作

时间:2022-07-22 09:20:29
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。

  源码:

jQuery-1.9.1源码分析系列(八) 属性操作jQuery-1.9.1源码分析系列(八) 属性操作
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源码如下

jQuery-1.9.1源码分析系列(八) 属性操作jQuery-1.9.1源码分析系列(八) 属性操作
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的图例

 jQuery-1.9.1源码分析系列(八) 属性操作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__" ) || "";
}

  完整源码  

jQuery-1.9.1源码分析系列(八) 属性操作jQuery-1.9.1源码分析系列(八) 属性操作
        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

 

 

   如果觉得本文不错,请点击右下方【推荐】!