1、jQuery提供了一些快捷函数来对dom对象的属性进行存取操作。
实例方法有:
jQuery.fn.extend({
attr
removeAttr
prop
removeprop
addClass
removeClass
toggleClass
hasClass
val
});
静态方法有:
jQuery.extend({
valHooks
attr
removeAttr
attrHooks
propFix
prop
propHooks
});
静态方法是内部使用的,特别是供实例方法调用,实例方法才是对外的。
这部分方法一般都有两个特点:
举例:
$("#div1").attr("title","hello") ,设置属性,两个参数时。
$("#div1").attr("title") , 获取属性值,一个参数时。
$("#div1").prop("title"),也可以获得这个属性值。
着重说明一下attr和prop的区别:
attribute:特性
property:属性
举例:
(1)、
$("#div1").attr("chaojidan","hello") ,给元素添加属性名为chaojidan的属性。在元素div标签上会显示chaojidan这个属性。
$("#div1").prop("chaojidan","hello"),也是给元素添加属性名为chaojidan的属性,但是在元素div标签上不会显示这个属性。
因为chaojidan是自定义属性,不是元素的固有属性。基本可以总结为attribute节点都是在HTML代码中可见的,而property只是一个普通的名值对属性
(2)、 $("#div1").attr("chaojidan") 返回hello。但是$("#div1").prop("chaojidan"),在有些浏览器下会返回空。因为chaojidan是自定属性。 (3)、对于a标签的href属性,attr返回href的属性值,但是prop返回document.URL + href的属性值。href是a标签的固有属性。 2、源码分析 attr方法和prop方法均调用了jQuery.access函数,jQuery.access主要作用是修正参数.access函数里的第二个参数jQuery.attr. 这个参数的作用是告诉access方法, 修正完参数后再去调用 jQuery.attr方法.access方法是可以被抽象出复用的一组对参数的修正方法,通过分解成单一的数据后,然后调用传递的回调处理钩子 比如 attr,css, prop.等等。 access源码:
3、一些核心jQuery函数都有自己的“插件API”称为“钩子” "钩子"是jQuery提供的API来调用用户自定义的函数,用于扩展,以便获取和设置特定属性的值。钩子机制是jQuery用来处理浏览器兼容的手法。钩子在.attr(), .prop(), .val() and .css() 四种操作中会涉及。 3.1 钩子机制:(以属性Attribute钩子举例) IE9-浏览器中,将input标签更改类型(type)为radio类型以后,value属性可能出现异常。所以我们定义了一个属性钩子(attrHooks)中类型(type)在更改设置(set)的一个处理。结构如下:
由上可知钩子结构为:钩子对象:{钩子类型:{钩子操作:xxx},……}(有的钩子类型可省略) 参考博客:https://blog.csdn.net/chen_hua89/article/details/50824529 3.2 钩子作用:
在做css3属性浏览器兼容的时候,都需要特定的前缀: Webkit的浏览器:-webkit-border-radius Firefox:-moz-border-radius 此时我看可以采用一个CSS hook 可以标准化这些供应商前缀的属性,让 给某一元素设置borderRadius,为10px 为了做浏览器兼容,我们不得不 这是一种最没技术含量的写法了,如果我们换成一种hook的话 borderRadius = styleSupport( "borderRadius" ); //获取到相对应的浏览器标准 3.3 钩子在attr和prop中的应用: jQuery.extend中涉及静态函数的源码: "for": "htmlFor", //htmlFor用于读取label标签的for属性 3.4 关于val方法和对其兼容的处理钩子函数valHook val方法的使用: $("#input1").val() //获取input元素的value属性值 $("#input1").val("hello") //设置input元素的value属性值为hello。 在对一个元素调用 .val() 函数时,首先取其 value 属性的值,如果没有的话,再使用其 text 值。 静态方法源码: 3.5 实例属性方法addClass、toggleClass、hasClass函数解析 方法的使用: $("#div1").addClass("box1 box2"); //给元素div的class属性添加box1和box2 $("#div1").removeClass("box1"); //删除元素div的class属性值box1 $("#div1").toggleClass("box1"); //如果元素div的class属性值中有box1,那么就删除box1。如果没有,那么就添加box1. $("#div1").hasClass("box1"); //元素div的class属性值是否有box1,如果有,就返回true,如果没有,就返回false。 源码:jQuery.fn.extend({
attr: function( name, value ) {
//此方法之前讲过,如果arguments.length > 1,就代表是设置操作,如果是false,那就代表是获取操作。
//而真正调用的回调方法是静态方法:jQuery.attr。name就是你传进来的属性名,value是你传进来的属性值。
return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
},
removeAttr: function( name ) {
return this.each(function() {
//实例方法removeAttr,调用的也是同名的静态方法removeAttr。
jQuery.removeAttr( this, name );
});
},
prop: function( name, value ) { //也是静态方法jQuery.prop
return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
},
removeProp: function( name ) {
return this.each(function() {
//删除属性,如果属性名需要做兼容,就做兼容,比如:class要变成className。
delete this[ jQuery.propFix[ name ] || name ];
});
},
//将对象传参分解成单一的参数从而set和get处理
access: function( elems, fn, key, value, chainable, emptyGet, raw ) {
var i = 0,
length = elems.length,
bulk = key == null;
// Sets many values
if ( jQuery.type( key ) === "object" ) { //传递是对象
chainable = true;
for ( i in key ) { //递归调用
jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
}
// Sets one value
} else if ( value !== undefined ) {
chainable = true;
if ( !jQuery.isFunction( value ) ) {
raw = true;
}
if ( bulk ) {
// Bulk operations run against the entire set
if ( raw ) {
fn.call( elems, value );
fn = null;
// ...except when executing function values
} else {
bulk = fn;
fn = function( elem, key, value ) {
return bulk.call( jQuery( elem ), value );
};
}
}
if ( fn ) {
for ( ; i < length; i++ ) {
fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
}
}
}
return chainable ?
elems :
// Gets
bulk ?
fn.call( elems ) :
length ? fn( elems[0], key ) : emptyGet;
},
//属性钩子对象(所有的属性钩子都放在里面)
attrHooks: {
//属性为type的钩子
type: {
//操作为set的钩子
set: function( elem, value ) {
if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
//IE6-9设置完type后恢复value属性(attr)
var val = elem.value;
elem.setAttribute( "type", value );
if ( val ) { elem.value = val; }
return value;
}
}
}
}
}
.css()
接受一个单一的,标准的属性的名称(border-radius
,或用DOM属性的语法,borderRadius
),判断的代码省略,直接看实现:$("#element").css("borderRadius", "10px");
if(webkit){
........................
}else if(firefox){
............................
}else if(...)更多
$.cssHooks.borderRadius = {
get: function( elem, computed, extra ) {
return $.css( elem, borderRadius );
},
set: function( elem, value) {
elem.style[ borderRadius ] = value;
}
};
attr: function( elem, name, value ) {
var hooks, ret,
nType = elem.nodeType;
//元素不存在或文本节点,或注释节点,或属性节点,不能设置属性,
//直接返回。元素节点才能设置属性。
if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
return;
}
//core_strundefined = "undefined"。document,window没有getAttribute方法,
//因此使用prop方法,而prop方法是用.属性名的形式。
if ( typeof elem.getAttribute === core_strundefined ) {
return jQuery.prop( elem, name, value );
}
//如果不是元素节点或元素节点是不是xml文档下的,如果是,那么jQuery.isXMLDoc( elem ) 返回true。
//这里的意思就是:如果是xml文档下的元素节点,就不会进入到if语句。xml文档下的元素都是自定义的,没有兼容性问题。
//所以不需要进入到if语句,进行兼容性处理。而html文档下的元素节点,有兼容性问题,所以需要做下处理。
if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
name = name.toLowerCase();
//hooks是jQuery中专门用来解决兼容性问题的。support用来检测浏览器的兼容性,hooks来解决兼容性问题,
//hooks针对不同的类型有相对应的hooks,比如:attr,就对应于attrHooks。
//hooks分两种,一种是针对设置的兼容性处理,set方法,一种是针对获取的兼容性处理,get方法。
//如果有兼容性问题,set方法或get方法会返回兼容性处理之后的值,如果没有兼容性问题,set就会返回undefined,get就会返回null。
//大家可以看下attrHooks对象,其实传属性名进来,只有type属性才有兼容性问题。而且只针对设置操作,获取操作没有兼容性问题。
//具体一点就是:设置type = "radio" 的兼容性问题。
hooks = jQuery.attrHooks[ name ] || ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
/*jQuery.expr = Sizzle.selectors,Sizzle.selectors对象中有match: matchExpr属性。
matchExpr也是一个对象,它里面的bool属性值是:new RegExp( "^(?:" + booleans + ")$", "i" )。
而booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped"。
nodeHook = undefined。如果不是在IE下设置type类型为radio,那么就会判断name是否匹配此正则。
如果匹配,就返回boolHook,而不匹配就会返回undefined。boolHook是用来专门处理bool类型属性的。
比如:, $("input").attr("checked") : checked,$("input").prop("checked") : true。
checked属性就属于bool类型属性。针对以上这个例子,我们知道attr获取checked的值是checked,
因为当我们设置是也应该$("input").attr("checked","checked"),但有些人可能对jQuery不熟,会写成$("input").attr("checked",true),
那么这种写法行不行呢,也是可以的,因为jQueyr里面做了兼容处理。其实就是boolHook对象,大家可以在文章的最后看到这个对象,看它是如何处理的。*/
}
if ( value !== undefined ) { //设置操作
if ( value === null ) { //$("#div1").attr("chaojidan",null)这种情况,会把chaojidan的属性移除。
jQuery.removeAttr( elem, name );
} else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
return ret; //如果有兼容性问题,就进行处理,然后把处理的值返回。
} else {
//用普通的方式,进行设置操作。因为属性值都是字符串,所以把number转化成字符串。
elem.setAttribute( name, value + "" );
return value;
}
} else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
return ret; //获取操作。先看是否有get的兼容性操作。
} else {
//jQuery.find = Sizzle。Sizzle中的attr方法对getAttribute 方法进行了兼容性处理。
ret = jQuery.find.attr( elem, name );
return ret == null ? undefined : ret;
}
},
removeAttr: function( elem, value ) {
var name, propName,
i = 0,
//core_rnotwhite = /\S+/g,这里的意思就是可以同时删除多个属性值,
//比如:$("div").removeAttr("id name class");,value = "id name class",
//调用match方法,并传入正则/\S+/g,会返回[id,name,class]。
attrNames = value && value.match( core_rnotwhite );
if ( attrNames && elem.nodeType === 1 ) { //必须是元素节点Element
while ( (name = attrNames[i++]) ) {
//propFix: {"for": "htmlFor","class":"className"},如果要删除的属性名是for或者class,那么需要做兼容处理。
//因此你做$("div").removeAttr("class")操作时,就不会出问题。
propName = jQuery.propFix[ name ] || name;
//如果要删除的属性名属于bool型的属性(也就是说它的值通过[属性名]获取时,是false或者true)
if ( jQuery.expr.match.bool.test( name ) ) {
//需要把此bool型的属性值赋为false,因为这个属性已经被移除了,不应该用[属性名]获取时,返回true。
//比如:input元素的checked属性,当你移除这个checked属性时,你通过input.checked获得true,
//那么就会被认为input中有这个checked,而这时checked你已经移除了,所以必须设置它的input.checked=false。
elem[ propName ] = false;
}
elem.removeAttribute( name ); //调用原生的方法移除掉
}
}
},
attrHooks: {
type: {
//这个方法是解决这样一个问题的:input = document.createElement("input");input.value = "t";
//input.type = "radio";support.radioValue = input.value === "t";
//当你先对input的value赋值,然后再设置input的type为radio时,IE下的input的value会变成on,而其他浏览器会得到t。
set: function( elem, value ) {
//如果存在以上这个兼容性问题,也就是jQuery.support.radioValue =false,IE下是false,value就是你设置type属性的值,并且元素是input,
//意思就是:你对input元素设置type=radio的操作。
if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
//怎么解决IE下的这个兼容性问题呢,我们先把这个input的value值保存起来。等设置了type = "radio" 后,再把值赋过去。
//这样它的input的value就不会变成on了。
var val = elem.value;
elem.setAttribute( "type", value );
if ( val ) {
elem.value = val;
}
return value;
}
}
}
propFix: {
if ( !jQuery.support.optSelected ) { //这里的hooks是针对select元素的第一个option元素是否会默认被选中。
//在IE下(老版本safari),不会默认选中,因此获取option的selected值时返回false, 而其他浏览器返回true。
jQuery.propHooks.selected = {
//只有get方法,因为只有获取时才会出现这个问题。假设你要获取option的selected属性值。
get: function( elem ) {
var parent = elem.parentNode;
if ( parent && parent.parentNode ) {
/*只要在获取option的selected的值时,先访问select.selectedIndex属性,
就可以设置option.selected = true了。意思就是在访问option的selected属性时,
先访问其父级select元素的selectedIndex属性,强迫浏览器计算option的selected属性,
以得到正确的值。需要注意的是option元素的父元素不一定是select,也有可能是optgroup。这里是支持IE9+,
所以option的parentNode是optgroup,optgroup的parentNode是select。*/
parent.parentNode.selectedIndex;
}
return null;
}
};
}
jQuery.each([ //不懂each方法的,可以看看前面博客关于静态方法each的解析
"tabIndex",
"readOnly",
"maxLength",
"cellSpacing",
"cellPadding",
"rowSpan",
"colSpan",
"useMap",
"frameBorder",
"contentEditable"
], function() {
//这里的this就是数组中的选项,比如:jQuery.propFix[ tabIndex.toLowerCase() ] = tabIndex;之所以这样做,是以防有人做jQuery属性操作时,
//把名字写成了全部是小写的情况,这里做下兼容,使用户输入全部是小写属性名,也能正常操作。
jQuery.propFix[ this.toLowerCase() ] = this;
});
val: function( value ) {
var hooks, ret, isFunction,
elem = this[0];
if ( !arguments.length ) { //如果是获取操作,也就是参数为0时.
if ( elem ) {
hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
//valHooks有以下几个属性对象:option(下拉框的子选项),select(下拉框),radio(单选按钮),checkbox(复选按钮)。
//也就意味着需要对这四种元素进行兼容性处理。其中radio的type=radio,checkbox的type=checkbox,select的type,默认为select-one(单选),
//还可以设置成select-multiple(,多选)。
if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
return ret;
}
ret = elem.value;
return typeof ret === "string" ? ret.replace(rreturn, "") : 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;
}
if ( val == null ) { //针对这种情况:$("input").val(null);
val = "";
} else if ( typeof val === "number" ) { //如果传入的是数字类型,就转换成字符串
val += "";
} else if ( jQuery.isArray( val ) ) {
//这里是针对checkbox,radio这种元素的,比如:$("#input2").val(["hello"]);这里如果传入的是字符串的话,是对checkbox的value属性赋值,
//但是传入数组,就代表checkbox的value是否等于hello,如果等于,就被选择上,如果不等于就不被选择上。
val = jQuery.map(val, function ( value ) {
return value == null ? "" : value + "";
});
}
hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { //请看下面的代码解释
this.value = val;
}
});
}
valHooks: {
option: {
/*当你获取option元素的value属性值时,如果没有对此option显式设置value值,获取到的值是option的text,也就是option的文本。
但是IE6-7下获取到的值是""。*/
get: function( elem ) {
var val = elem.attributes.value; //在IE6-7下,val是一个object。
return val.specified ? elem.value : elem.text;
//如果val.specified为true,就代表value被显式设置了,因此直接返回elem.value,如果为false,就代表没有显式设置,因此返回elem.text。
}
},
select: {
get: function( elem ) {
//当select是单选时,获取的value值,就是你选择的那个option的值,如果是多选,获取值时,就是你选择的所有option的值的数组形式。
var value, option,
options = elem.options, //select的所有option的集合。
index = elem.selectedIndex, //当前选择的option的索引值
one = elem.type === "select-one" || index < 0,
values = one ? null : [], //如果是单选,values=null,如果是多选,values=[]。
max = one ? index + 1 : options.length,
i = index < 0 ? max : one ? index : 0;
for ( ; i < max; i++ ) { //单选,循环一次,多选,循环多次
option = options[ i ];
if ( ( option.selected || i === index ) &&
//IE6-9下,点击reset按钮时,option的selected不会恢复默认值,其他浏览器会恢复所有option的selected的默认值。
( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
//如果option被设置了disabled,那么获取option的值时,是获取不到的。
( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
//如果option的父元素被设置了disabled,并且父元素是optgroup,那么也获取不到。
value = jQuery( option ).val();
if ( one ) {
return value;
}
values.push( value );
}
}
return values;
},
set: function( elem, value ) {
var optionSet, option,
options = elem.options,
values = jQuery.makeArray( value ), //把value转换成数组
i = options.length;
while ( i-- ) {
option = options[ i ];
if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {
//判断select的子元素option的value是否在values数组中,如果在,就会把这个option选中。
optionSet = true;
}
}
if ( !optionSet ) {
elem.selectedIndex = -1;
//如果select下的option的value值没有一个等于value的,那么就让select的选择索引值赋为-1.让select框中没有任何值。
}
return values;
}
}
}
// Radios and checkboxes getter/setter
jQuery.each([ "radio", "checkbox" ], function() {
jQuery.valHooks[ this ] = {
set: function( elem, value ) {
if ( jQuery.isArray( value ) ) {
//当value是数组时,看此元素的value值是否在数组value中,如果在就让元素被选择上。此元素只有radio,checkbox这两种。
return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
}
}
};
if ( !jQuery.support.checkOn ) {
//如果元素是radio或者checkbox,我们去获取它的默认value值时,老版本webkit得到的值是"",
//而其他浏览器是on,因此当没有对此元素显式设置它的value值时(通过getAttribute获取的value的是null),
//我们通过input.value获取它的默认值,所有浏览器都返回on。
jQuery.valHooks[ this ].get = function( elem ) {
return elem.getAttribute("value") === null ? "on" : elem.value;
};
}
});
// 为匹配的每个元素增加指定的class(es)
addClass: function( value ) {
var classes, elem, cur, clazz, j,
i = 0,
len = this.length, //this指的是$("div")
proceed = typeof value === "string" && value;
//判断传入的参数是否是字符串。我们在例子中,传入的都是字符串的形式,其实此方法,还可以传入回调方法,
//比如:$("div").addClass(function(index){ return "box"+index; }) ,回调方法的返回值,将会作为addClass的参数传入。
//这段代码就会在第一个div的class属性中添加box0,在第二个div的class属性中添加box1,以此类推。
if ( jQuery.isFunction( value ) ) { //传入的参数是否是函数
return this.each(function( j ) {
jQuery( this ).addClass( value.call( this, j, this.className ) );
//回调方法的第一个参数是当前元素的index值,第二个参数是当前元素的class属性值。
});
}
if ( proceed ) { //如果是字符串
classes = ( value || "" ).match( core_rnotwhite ) || [];
// core_rnotwhite = /\S+/g,把"box1 box2"转换成[box1,box2]
for ( ; i < len; i++ ) { //循环元素
elem = this[ i ];
cur = elem.nodeType === 1 && ( elem.className ? ( " " + elem.className + " " ).replace( rclass, " " ) :" ");
//如果是元素节点,就继续进行判断元素的class属性值是否存在,rclass = /[\t\r\n\f]/g,\t是制表符,\r是回车,\n换行符,\f是换页。
//这些都是空白符,不是空格,我们需要把空白符替换成空格,以防元素的class属性值之间用空白符隔开,而不是空格隔开的。
//比如: