今天主要分析一下jQuery设置元素DOM属性的相关方法的具体实现:addClass()、attr()、hasClass()、html()、prop()、removeAttr()。
首先是addClass():
// 该方法用于向元素添加新类名
addClass: function( value ) {
var classNames, i, l, elem,
setClass, c, cl;
// 如果参数是函数的话,执行该分支语句
if ( jQuery.isFunction( value ) ) {
// each方法在jQuery中经常用到,因为我们操作的jQuery对象经常是多个,该方法可以让每个jQuery对象都执行相应的操作
return this.each(function( j ) {
/* 这里用到了Function.call(),这也是在jQuery内部实现中经常用到的
还有与之类似的Function.apply()。二者都是用于改变运行上下文,
借用已存在的方法进行操作,不同点就是传递参数的方式
*/
jQuery( this ).addClass( value.call(this, j, this.className) );
});
}
// 如果参数是字符串的话,执行该分支语句
if ( value && typeof value === "string" ) {
// core_rspace = /\s+/,用于匹配一个及以上的空白字符
classNames = value.split( core_rspace ); // 将提供的参数按空白字符分开放到一个数组里
for ( i = 0, l = this.length; i < l; i++ ) {
elem = this[ i ];
// 确保元素是元素节点
if ( elem.nodeType === 1 ) {
// 如果元素的class类名不存在而且新添加的类名只有一个
if ( !elem.className && classNames.length === 1 ) {
elem.className = value; // 直接将参数赋值给class属性
} else {
// 在元素原有的class类名字符串前后添加空白字符可以保证添加新的类名后和原来的类名不会连到一块,从而导致错误
setClass = " " + elem.className + " ";
for ( c = 0, cl = classNames.length; c < cl; c++ ) {
// 确保新添加的className在原来的元素的class属性中不存在
if ( setClass.indexOf( " " + classNames[ c ] + " " ) < 0 ) {
setClass += classNames[ c ] + " ";// 加“ ”可以保证类名不会连接到一块
}
}
// jQuery.trim方法用于去除字符串前后空格
elem.className = jQuery.trim( setClass );
}
}
}
}
// 形成链式调用
return this;
}
该方法为每个匹配的元素添加指定的类名,共有两种使用方法:
addClass( className )
className为每个匹配元素所要增加的一个或多个样式名。
- addClass(function(index, currentClass))
function(index, currentClass)这个函数返回一个或更多用空格隔开的要增加的样式名。接收元素的索引位置index和元素旧的样式名currentClass作为参数。
在该方法里有一个易错点,就是访问元素的class属性不可用elem.class,因为class是ECMAScript规范中的保留字,在将来可能会在语言规范中用到,所以需要用elem.className。与之相似的是访问元素style属性的float属性,需要用elem.style.cssFloat和elem.style.styleFloat,还有for属性,应该用htmlFor。
attr()方法:
attr: function( name, value ) {
return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
}
怎么样,看着简单吧?该方法的作用就是取得第一个匹配元素的属性值或者为指定元素设置一个或多个属性。但是将该方法可以衍生出许多高级用法。不过我们一般就是用于直接设置和获取属性值。与之类似的还有prop()方法和html()方法:
prop: function( name, value ) {
return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
}
html: function( value ) {
return jQuery.access( this, function( value ) {
var elem = this[0] || {},
i = 0,
l = this.length;
if ( value === undefined ) {
return elem.nodeType === 1 ?
elem.innerHTML.replace( rinlinejQuery, "" ) :
undefined;
}
// See if we can take a shortcut and just use innerHTML
if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) &&
( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
!wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
value = value.replace( rxhtmlTag, "<$1></$2>" );
try {
for (; i < l; i++ ) {
// Remove element nodes and prevent memory leaks
elem = this[i] || {};
if ( elem.nodeType === 1 ) {
jQuery.cleanData( elem.getElementsByTagName( "*" ) );
elem.innerHTML = value;
}
}
elem = 0;
// If using innerHTML throws an exception, use the fallback method
} catch(e) {}
}
if ( elem ) {
this.empty().append( value );
}
}, null, value, arguments.length );
}
prop方法一般用于获取在匹配的元素集中的第一个元素的属性值。高级用法是设置为匹配的元素设置一个或更多的属性。
html方法用于从匹配的第一个元素中获取或设置HTML内容。
这三个方法都用到了jQuery.access方法:
该方法当设置/获取属性的时候遍历jQuery对象,依次调用fn函数。这里的fn可能是jQuery.attr和jQuery.prop。需要注意的是这里用for遍历elems,这个elems实际是jQuery对象,jQuery对象它是一个ArrayLike。大概就是如此,剩下的一堆hooks是用来解决浏览器兼容性的问题。如jQuery.attrHooks、jQuery.propHooks、jQuery.valHooks。列举如下
- IE6/7不完全支持setAttribute,比如不能设置class,for等属性
- IE6/7中getAttribute获取href / src 属性(相对路径)值与其它浏览器不同
- IE6/7中获取Button元素的值有bug
- IE6/7不支持setAttribute设置style / 不支持getAttribute获取style属性值
- 获取tabIndex属性有差异
- 布尔类型属性的修复,如selected,disabled,checked等
- select和option元素的值修复
也许以后我会专门写篇博客来分析这个方法。它是一个多功能的方法,用于获取和设置集合中的值,我们只要知道这一点就行了。
hasClass():
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;
}
该方法用于检查匹配的元素是否含有某个特定的类selector。将参数selector转换为" " + selector + " "是为了防止如果元素的class属性中如果出现“sub selector-sup”的情况,这时如果直接用selector匹配的话,因为“selector-sup”的出现就会导致匹配正确,但实际上元素的class属性中并没有selector类名,只不过class属性字符串中存在该字符串,从而导致错误。在参数前后添加空格后可以避免这个错误,但同时我们知道在元素的class属性中,前后两个类名的前/后面是没有空格的,所以我们需要把className经过" " + this[i].className + " "处理。l = this.length;用于缓存操作的jQuery对象集合的长度,这种方式使我们在写JavaScript中应该存在的意识,尤其是在DOM操作中。for循环是为了让元素集合中的每一项都执行该操作,在这里就是每个都检查一下是否存在selector类名,如果其中有一项不符合,则整个不匹配。在for循环中,this[i].nodeType === 1可以保证我们将要操作的节点是元素节点,而不是文本节点等其他节点,jQuery这种安全保障机制是值得我们学习的。replace(rclass, " ")中的rclass = /[\t\r\n]/g,是把class属性中的制表符、回车符、换行符替换成空格,形成统一的“ class1 class2 class3 class4 class5 ”效果,然后再indexOf检索特定的className是否存在。这还是一种安全保障机制,jQuery真是步步为营!
removeAttr()方法:
removeAttr: function( name ) {
return this.each(function() {
jQuery.removeAttr( this, name );
});
}
该方法只接受一个参数,为匹配的元素集合中的每个元素中移除一个name属性。这里用到了jQuery.removeAttr():
/*
这里用到了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"
},它用于修正IE浏览器中的IE6、7getAttribute、setAttribute、removeAttribute等方法的不足
*/
removeAttr: function( elem, value ) {
var propName, attrNames, name, isBool,
i = 0;
// 确保要移除的属性存在,并且节点是元素节点
if ( value && elem.nodeType === 1 ) {
// 将参数value按空格分解成数组
attrNames = value.split( core_rspace );
// 对要移除的属性数组一个一个移除
for ( ; i < attrNames.length; i++ ) {
name = attrNames[ i ];
if ( name ) { // 确保要移除的属性存在非空
propName = jQuery.propFix[ name ] || name; // 修正IE部分浏览器获取属性的错误
// rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i
isBool = rboolean.test( name ); // 检查要设置的属性是否是boolean值控制的
// 直接对非boolean值控制的属性进行赋空值,也就是移除
if ( !isBool ) {
jQuery.attr( elem, name, "" );
}
// div = document.createElement("div"); getSetAttribute: div.className !== "t"检查是否支持setAttribute,主要针对IE6/7
elem.removeAttribute( getSetAttribute ? name : propName );
// 如果该属性由boolean值控制,并且修正后的属性是元素的属性,双重保险
if ( isBool && propName in elem ) {
elem[ propName ] = false;
}
}
}
}
}
明天继续分析removeClass()、removeProp()、toggleClass()、val()。