jQuery 的属性操作的核心部分其实就是对底层 getAttribute()、setAttributes()等方法的一系列兼容性处理

...
if ( notxml ) {
name = name.toLowerCase();
hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
} if ( value !== undefined ) { if ( value === null ) {
jQuery.removeAttr( elem, name );
return; } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
return ret; } else {
elem.setAttribute( name, value + "" );
return value;
} } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
return ret; } else { ret = elem.getAttribute( name ); // Non-existent attributes return null, we normalize to undefined
return ret === null ?
undefined :
ret;
}
... 

在方法的内部会用一系列的钩子(HOOKS)来把需要处理的情况约定好,如 attrHooks 等,钩子的结构都大致类似

var someHook = {
get: function(elem) {
// obtain and return a value
return "something";
},
set: function(elem, value) {
// do something with value
}
}

最后通过 access 方法来修正传入的参数使 get、set 一体化,然后暴露给外部 API

关于 access 的内容会在下期中进行介绍

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

jQuery.attr: function( elem, name, value, pass )

前三个参数不需要多说,最后一个参数 pass 是用来判断在 HTML 属性与 jQuery 方法同名时是否调用同名的 jQuery 方法的

if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) {
return jQuery( elem )[ name ]( value );

如果 getAttribute 方法挂了,就转用 prop 方法

// Fallback to prop when attributes are not supported
if ( typeof elem.getAttribute === "undefined" ) {
return jQuery.prop( elem, name, value );

attrHooks专门处理一些特殊的属性

在它定义的地方定义了对 type 和 value 的处理

  • type: jQuery.support.radioValue 为 false 时,在设置 input 的 type 为 radio的时候要先把 value 备份一份,然后再写回去
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;
}
}
}
},
  • value:jQuery 把 value 的 set 和 get 都交给了nodeHook 来处理,这里解决的是 IE6/7的 button 元素的 DOM 属性 value
value: {
get: function( elem, name ) {
if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
return nodeHook.get( elem, name );
}
return name in elem ?
elem.value :
null;
},
set: function( elem, value, name ) {
if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
return nodeHook.set( elem, value, name );
}
// Does not return so that setAttribute is also used
elem.value = value;
}
}
  • 在代码的后面仍然有几个 attrHooks 的处理情况,是通过属性的方式挂载到了 attrHooks 上面,有兴趣的童鞋可以参考 jQuery.support 一章关于这里的说明
// Set width and height to auto instead of 0 on empty string( Bug #8150 )
// This is for removals
jQuery.each( [ "width", "height" ], function( i, name ) {
jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
set: function( elem, value ) {
if ( value === "" ) {
elem.setAttribute( name, "auto" );
return value;
}
}
} );
} ); // Set contenteditable to false on removals(#10429)
// Setting to empty string throws an error as an invalid value
jQuery.attrHooks.contenteditable = {
get: nodeHook.get,
set: function( elem, value, name ) {
if ( value === "" ) {
value = "false";
}
nodeHook.set( elem, value, name );
}
};
} // Some attributes require a special call on IE
if ( !jQuery.support.hrefNormalized ) {
jQuery.each( [ "href", "src", "width", "height" ], function( i, name ) {
jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
get: function( elem ) {
var ret = elem.getAttribute( name, 2 );
return ret === null ? undefined : ret;
}
} );
} );
} if ( !jQuery.support.style ) {
jQuery.attrHooks.style = {
get: function( elem ) {
// Return undefined in the case of empty string
// Normalize to lowercase since IE uppercases css property names
return elem.style.cssText.toLowerCase() || undefined;
},
set: function( elem, value ) {
return ( elem.style.cssText = value + "" );
}
};
}

boolHook 专门处理如下布尔型的 HTML 属性

bool: /^(?:checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped)$/i

内部具体实现逻辑比较简单就不多说了,需要注意的一点是在 get 的时候,jQuery 用的是 prop 方法来取得的值

property = jQuery.prop( elem, name );

nodeHook 专门用来处理 IE6/7这两个傻X的 get/setAttribute 的问题,这里采用属性节点来直接设置和读取元素属性值

// IE6/7 do not support getting/setting some attributes with get/setAttribute
if ( !getSetAttribute ) { //这三个属性如果为空,则需要返回 undefined
fixSpecified = {
name: true,
id: true,
coords: true
}; // Use this for any attribute in IE6/7
// This fixes almost every IE6/7 issue
nodeHook = jQuery.valHooks.button = {
get: function( elem, name ) {
var ret;
ret = elem.getAttributeNode( name );
return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ?
ret.value :
undefined;
},
set: function( elem, value, name ) {
// Set the existing or create a new attribute node
var ret = elem.getAttributeNode( name );
if ( !ret ) {
ret = document.createAttribute( name );
elem.setAttributeNode( ret );
}
return ( ret.value = value + "" );
}
};

hook 约定完成以后,开始正式处理元素属性的设置、删除和读取,判断的逻辑主要围绕 value 的值

if ( value !== undefined ) {//注意这里是全等,所以 null 在这里是不行的( null == undefined )

    if ( value === null ) {//这种方式可以把值删掉
jQuery.removeAttr( elem, name );
return; } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {//这里直接进行了赋值操作
return ret; } else {
elem.setAttribute( name, value + "" );
return value;
} } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {//同样,这里直接进行了取值操作
return ret; } else { ret = elem.getAttribute( name );//都不需要特殊处理,就直接调用原始的方法 // Non-existent attributes return null, we normalize to undefined
// 这里统一把不存在的属性值处理成了 undefined
return ret === null ?
undefined :
ret;
}

jQuery.removeAttr: function( elem, value )

这个方法支持空格分隔的属性名称,所以一次可以移除多个属性

attrNames = value.split( core_rspace );

这个方法主要做了三个事情:

  • 移除前把属性的值置空,用来解决 Webkit 内核浏览器不能移除 HTML 属性 style 的问题  
// See #9699 for explanation of this approach (setting first, then removal)
// Do not do this for boolean attributes (see #10870)
if ( !isBool ) {
jQuery.attr( elem, name, "" );
}
  • get/setAttribute 方法不支持时,会把 HTML 属性对应的 DOM 属性传入进去
//DOM属性必须是驼峰的
propName = jQuery.propFix[ name ] || name;
...
elem.removeAttribute( getSetAttribute ? name : propName );
...
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"
}

布尔类型的 HTML 属性,需要同步设置对应的 DOM 属性为 false

// Set corresponding property to false for boolean attributes
if ( isBool && propName in elem ) {
elem[ propName ] = false;
}

jQuery.prop: function( elem, name, value )

prop方法的处理过程与attr方法大同小异,最大的不同表现在一些hooks的处理上,具体实现不在赘述

propHooks原始定义

propHooks: {
tabIndex: {
get: function( elem ) {
// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
var attributeNode = elem.getAttributeNode("tabindex"); return attributeNode && attributeNode.specified ?
parseInt( attributeNode.value, 10 ) :
rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
0 :
undefined;
}
}
}

propHooks的一些扩展

// Safari mis-reports the default selected property of an option
// Accessing the parent's selectedIndex property fixes it
if ( !jQuery.support.optSelected ) {
jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
get: function( elem ) {
var parent = elem.parentNode; if ( parent ) {
parent.selectedIndex; // Make sure that it also works with optgroups, see #5701
if ( parent.parentNode ) {
parent.parentNode.selectedIndex;
}
}
return null;
}
});
}

.removeProp: function( name )

prop属性的删除方法比较简单,所以jQuery没有对它再进行一层封装

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 )

addClass: function( value ) {
var classNames, i, l, elem,
setClass, c, cl; if ( jQuery.isFunction( value ) ) {
return this.each(function( j ) {
//jQuery1.4开始,这个方法开始可以接受一个函数作为参数
//函数中的this被指向了当前的这个dom元素
//并且接收元素在当前set中的序号和原始的class名称作为参数
jQuery( this ).addClass( value.call(this, j, this.className) );
});
} if ( value && typeof value === "string" ) {
//空格分离的类名
classNames = value.split( core_rspace ); for ( i = 0, l = this.length; i < l; i++ ) {
elem = this[ i ];
//只接受元素节点
if ( elem.nodeType === 1 ) {
//最简单的情况,元素还没有任何类名
//这种情况就不在进行for循环了
//从这一句简单的代码也可以看出jQuery的一个很重要的代码风格,就是从来不做多余的事情
          //判断长度是为了避免重复的类名 
//这里和后面可以看出对元素类名的赋值使用的都是className属性
if ( !elem.className && classNames.length === 1 ) {
elem.className = value; } else {
//不是最简单的情况
//首先把现有的类名用约定的格式封装起来,作为拼接结果的第一项
setClass = " " + elem.className + " "; for ( c = 0, cl = classNames.length; c < cl; c++ ) {
//避免添加重复的类名进去
if ( setClass.indexOf( " " + classNames[ c ] + " " ) < 0 ) {
setClass += classNames[ c ] + " ";
}
}
//剔除首尾空格
elem.className = jQuery.trim( setClass );
}
}
}
} //保证可以进行链式调用
return this;
},

.removeClass: function( value )

这个方法基本就是addClass方法的逆过程,不同的是,这个方法我们如果什么参数都不传,它会把所有的类名都删除掉

jQuery官方的说明

If a class name is included as a parameter, then only that class will be removed from the set of matched elements. If no class names are specified in the parameter, all classes will be removed.

相关的代码片段

...
if ( (value && typeof value === "string") || value === undefined ) {
...
elem.className = value ? jQuery.trim( className ) : "";
...

.toggleClass: function( value, stateVal )

这是一个稍微有点复杂的方法,先看官网对它最新的说明

基本用法、接受的参数等与addClass等方法基本类似,最大的不同在于多出来的state参数,用于手动控制元素类名的增删

另外,在官方的文档中,有一段话是值得关注的:

As of jQuery 1.4, if no arguments are passed to .toggleClass(), all class names on the element the first time .toggleClass() is called will be toggled. Also as of jQuery 1.4, the class name to be toggled can be determined by passing in a function.

意思是这个方法同样可以什么参数都不传,它的作用在当前元素第一次调用的时候类似于removeClass方法,会把现存的所有类名清空,那么如果再链式调用一次这个方法呢?

toggleClass: function( value, stateVal ) {
var type = typeof value,
isBool = typeof stateVal === "boolean"; if ( jQuery.isFunction( value ) ) {
return this.each(function( i ) {
//传入函数的用法,这里把stateVal参数也传了进去
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.split( core_rspace ); //又是一种巧妙的用法,集合自增循环、赋值、判断于一句话
while ( (className = classNames[ i++ ]) ) {
// check each className given, space separated list
// 状态参数的优先级高于hasClass
state = isBool ? state : !self.hasClass( className );
self[ state ? "addClass" : "removeClass" ]( className );
} } else if ( type === "undefined" || type === "boolean" ) {
if ( this.className ) {
// store className if set
// 这里把原始的类名缓存了起来
jQuery._data( this, "__className__", this.className );
} // toggle whole className
// 最特么烦jQuery这种写法,多写几个if会死啊!!
/* 分解一下,大概可以分为以下这么几种情况
* 1、如果this.className存在,总是置空,不受switch值的影响
* 2、如果this.className不存在,switch为false时类名保持不变(置空),为true或undefined时为jQuery._data( this, "__className__" ) || ""
*/
this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
}
});
},

所以链式零参数的调用时,会把元素的类名恢复回来.

.hasClass: function( selector ) 

用法不多说,有一点需要注意的是,hasClass方法在检测类名时,是把传入的selector作为一个整体来检测的,并没有按照空格分离的操作,这一点需要注意

相关的代码片段

var className = " " + selector + " ",

.val: function( value )

这个方法与上面的这些方法大同小异,有一点我不太明白的是这个方法为什么没有接入 access 方法呢?

jQuery属性操作的更多相关文章

  1. jquery——属性操作、特殊效果

    1. attr().prop() 取出或者设置某个属性的值 <!DOCTYPE html> <html lang="en"> <head> &l ...

  2. jQuery源代码学习之八——jQuery属性操作模块

    一.jQuery属性模块整体介绍 jQuery的属性操作模块分四个部分:html属性操作,dom属性操作,类样式操作,和值操作. html属性操作(setAttribute/getAttribute) ...

  3. jQuery 属性操作和CSS 操作

    如有在jQuery方法中涉及到函数,此函数必定会返回一个数值(函数由于运行次数不同触发一些不同效果) jQuery 属性操作方法(以下方法前些日子学习过,不再赘述) addClass() attr() ...

  4. python全栈开发day48-jqurey自定义动画,jQuery属性操作,jQuery的文档操作,jQuery中的ajax

    一.昨日内容回顾 1.jQuery初识 1).使用jQuery而非JS的六大理由 2).jQuery对象和js对象转换 3).jQuery的两大特点 4).jQuery的入口函数三大写法 5).jQu ...

  5. jQuery属性操作(四)

    通过阅读jQuery为属性操作封装的基本方法和为处理兼容性问题提供的hooks,发现jQuery在属性操作方面并没有做过多的设计,只是处理一下兼容性问题,然后调用基础的DOM操作方法.以下是对JQue ...

  6. jQuery属性操作(二)

    挂载到$上的几个属性操作方法分析,发现属性操作用到了sizzle封装的方法 attr: function( elem, name, value ) {        var hooks, ret,   ...

  7. jQuery属性操作(一)

    下载了jQuery的UI组件,发现内容还挺多的,还是决定先把jQuery的源码看完一遍之后再涉足UI组件.考虑到队列和动画使用较少,特别是动画,基本开始使用css3完成.因此暂时略过,开始看jQuer ...

  8. web前端----jQuery属性操作

    知识点总结 1.属性 属性(如果你的选择器选出了多个对象,那么默认只会返回出第一个属性). attr(属性名|属性值) - 一个参数是获取属性的值,两个参数是设置属性值 - 点击加载图片示例 remo ...

  9. Jquery属性操作(入门二)

    ********JQuery属性相关的操作******** 1.属性 属性(如果你的选择器选出了多个对象,那么默认只会返回出第一个属性). attr(属性名|属性值) - 一个参数是获取属性的值,两个 ...

随机推荐

  1. MongoDB 3.X 用户权限控制

    摘要: MongoDB 3.0 安全权限访问控制,在添加用户上面3.0版本和之前的版本有很大的区别,这里就说明下3.0的添加用户的方法. 环境.测试: 在安装MongoDB之后,先关闭auth认证,进 ...

  2. Google Gson 使用简介

    http://www.cnblogs.com/haippy/archive/2012/05/20/2509329.html

  3. PHP 实现数学问题:组合

    需求: 有一个数组 ['a', 'b', 'c', 'cd'] 需要从数组中取出任意 m 个不重复的元素,列出所有的可能性(也不需要出现重复的组合例如['a', 'b' ,'c'] 和 ['a', ' ...

  4. LWL-Hitokoto API(一言-纯净API)

    著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处.作者:liwanglin12链接:https://blog.lwl12.com/read/hitokoto-api.html来源:L ...

  5. find_elements后点击不了抓取的元素

    1.莫名其妙抓不到元素,要去看句柄,是不是没有切换 h=driver.current_window_handle nh=driver.window_handles for i in nh: if i! ...

  6. web系统登陆页面增加验证码

    传统登陆页面中包含两个输入项: • 用户名 • 密码有时为了防止机器人进行自动登陆操作,或者防止恶意用户进行用户信息扫描,需增加动态验证码功能.此时,登陆页面中包含了三个输入项: • 用户名 • 密码 ...

  7. 《linux内核设计与实现》读书笔记第十七章

    第17章 设备与模块 四种内核成分 设备类型:在所有 Unix 系统中为了统一普通设备的操作所采用的分类. 模块: Linux 内核中用于按需加载和卸载目标码的机制. 内核对象:内核数据结构中支持面向 ...

  8. node静态资源管理变迁之路

    使用express自带的,express.static,如:app.use(express.static('hehe')),就可以用localhost/hua.png,访问项目根目录下,hehe文件夹 ...

  9. 有关windows系统的EXE和DLL文件说法错误

    正确答案: B C   你的答案: C (错误) EXE和DLL文件都是PE文件 EXE不能有导出函数,DLL可以有导出函数 EXE有x86和x64之分,则DLL没有 EXE可以单独运行,DLL则不行 ...

  10. .csv导入mysql时出现乱码