jQuery源码 Attr

介绍

   在日常开发中,开发人员最常使用getAttribute()、setAttribute()和removeAttribute(),或者框架封装的属性读写方法(如attr()),却很少直接引用节点的属性节点;这让我感觉要读取属性的值就必须调用节点的这些方法,DOM节点和其他的js对象不一样,元素的特性不像其他对象属性一样,可以直接访问;

   直到今天看网上的placeholder兼容代码时发现,仅仅通过属性存在性判断浏览器是否支持placeholder属性,贴上源码

if (!('placeholder' in document.createElement('input'))) { ... }

这么一看DOM节点对象和其他的javascript对象是类似的,后试验的确可以通过[]、.等操作符来获得或设置节点的属性值;于是心中产生疑问,

1、为什么我们平常的js很少看见直接通过element.attribueName来获得或设置属性值,而使用原生的getAttribute方法;

2、jQuery的attr方法又做了哪些事情呢

 

问题一经过解答,大概的概念就是“这是一种习惯,是w3c推荐的用法”,分析一下

      element.attributeName直接引用节点属性,这时节点就更像一个obj对象,而不是node节点,attribute是一个对象的属性

      getAttribute()方法,element节点是继承于node节点,是一个树节点,attr也是继承于node节点,通过getAttribute()更像XML中遍历树节点,然后获得指定节点的文本

这样一看getAttribute()方法是一个原生的方法,而直接引用节点属性更像是各浏览器封装后扩展的特性

不难想象为什么日常开发中的偏向了,不过getAttribute()方法在各浏览器中有些属性的支持也有些不一致,这是下面要讲的jQuery.attr()

 

.attr()

.attr(attributeName) 取得第一个匹配元素的属性值(当属性没有被设置时,返回undefined,不能用在文本节点、注释节点、属性节点上)

.attr(attributeName, value) 设置单个属性

.attr(map) 设置多个属性

.attr(attributeName, function(index, attr)) 通过函数的返回值设置属性

jQuery.extend({
attr: function( elem, name, value, pass ) {
var nType = elem.nodeType;

// 不支持文本节点、注释节点、属性节点
if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
return undefined;
}
// 判断属性名是否在jQuery提供的方法中,是则直接调用方法(该判断估计是保留内部方法调用时使用的,因为api文档中并没有提到pass参数)
if ( pass && name in jQuery.attrFn ) {
return jQuery( elem )[ name ]( value );
}

// 不支持getAttribute()时调用prop(),直接引用节点属性获取和设置属性值
if ( !("getAttribute" in elem) ) {
return jQuery.prop( elem, name, value );
}

var ret, hooks,
notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); // 判断documentElement是否存在

// 修正name
if ( notxml ) {
name = jQuery.attrFix[ name ] || name;

hooks = jQuery.attrHooks[ name ];

if ( !hooks ) {
// boolean值的属性处理
if ( rboolean.test( name ) ) {
hooks = boolHook;

// IE6/7 不支持getAttribute属性的处理
} else if ( nodeHook ) {
hooks = nodeHook;
}
}
}

// 先特殊处理,再getAttribute和setAttribute
if ( value !== undefined ) {

if ( value === null ) {
jQuery.removeAttr( elem, name );
return undefined;

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

// 不存在的属性值返回undefined
return ret === null ?
undefined :
ret;
}
},
...
});

从源码可以看出.attr()的处理过程,先特殊处理各种特殊情况,再用约定getAttribute()和setAttribute()方法,下面继续贴各种特殊处理源码

先介绍各个对象的作用

// 直接引用属性节点的fix
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"
}

attrFix: {
// Always normalize to ensure hook usage
tabindex: "tabIndex"
},

rtype = /^(?:button|input)$/i, // button节点和input节点
//
bool值的属性节点
rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
nodeHook,  // 通过nodeValue获取设置属性值得对象
boolHook,  // 处理bool属性的对象

直接引用属性节点

prop: function( elem, name, value ) {
var nType = elem.nodeType;

// 不支持文本节点、注释节点、属性节点
if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
return undefined;
}

var ret, hooks,
notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); // 判断节点是不是XML节点

if ( notxml ) {
// 修正name,js支持的属性名
name = jQuery.propFix[ name ] || name;
hooks = jQuery.propHooks[ name ]; // 目前只支持tabIndex的特殊处理
}

if ( value !== undefined ) { // 设置属性值
if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
return ret;

} else {
return (elem[ name ] = value);
}

} else { // 获取属性值
if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
return ret;

} else {
return elem[ name ];
}
}
}

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;
}
}
}

type和value属性的特殊处理

attrHooks: {
type: {
set: function( elem, value ) {
// 不允许修改type属性,其在ie下会出问题(因为ie下的type属性是只读的)
if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
jQuery.error( "type property can't be changed" );
} else 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 it's default in case type is set after value
// This is for element creation
var val = elem.value;
elem.setAttribute( "type", value );
if ( val ) {
elem.value = val;
}
return value;
}
}
},
// Use the value property for back compat
// Use the nodeHook for button elements in IE6/7 (#1954)
value: {
get: function( elem, name ) {
if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
return nodeHook.get( elem, name );
}
// 确保元素有name属性,才读取其value值
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;
}
}
}

nodeHook的定义

// IE6/7 某些属性不支持getAttribute和setAttribute
//
通过getAttributeNode先获得属性节点,再通过nodeValue获得/设置节点的值
//
未存在的属性,先创建属性节点再赋值
if ( !jQuery.support.getSetAttribute ) {

// 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 undefined if nodeValue is empty string
return ret && ret.nodeValue !== "" ?
ret.nodeValue :
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.nodeValue = value + "");
}
};
}

boolHook的定义

boolHook = {
get: function( elem, name ) {
// Align boolean attributes with corresponding properties
// Fall back to attribute presence where some booleans are not supported
var attrNode;
return jQuery.prop( elem, name ) === true || ( attrNode = elem.getAttributeNode( name ) ) && attrNode.nodeValue !== false ?
name.toLowerCase() :
undefined;
},
set: function( elem, value, name ) {
var propName;
if ( value === false ) {
// Remove boolean attributes when set to false
jQuery.removeAttr( elem, name );
} else {
// value is true since we know at this point it's type boolean and not false
// Set boolean attributes to the same name and set the DOM property
propName = jQuery.propFix[ name ] || name;
if ( propName in elem ) {
// Only set the IDL specifically if it already exists on the element
elem[ propName ] = true;
}

elem.setAttribute( name, name.toLowerCase() );
}
return name;
}
};

现在可以很好地解答第二个问题了,.attr()帮我们做很多兼容的处理,引用api中的原话,.attr()主要有两个好处

1、方便:它可以被称直接jQuery对象访问和链式调用其他jQuery方法。

2、浏览器兼容:一些属性在浏览器和浏览器之间有不一致的命名。 此外,一些属性的值在不同的浏览器中报道也是不一致的(英文原文: the values of some attributes are reported inconsistently across browsers), 甚至在同一个浏览器的不同版本中。 .attr() 方法减少了兼容性问题。

总结

   js中麻烦的还是兼容性的处理,后面的文章再实验看看.attr()方法中这些特殊处理对应的属性有哪些

posted @ 2012-03-07 23:35  okbeng  阅读(1334)  评论(1编辑  收藏  举报