介绍

   在日常开发中,开发人员最常使用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()方法中这些特殊处理对应的属性有哪些