代码改变世界

jQuery源码分析:jQuery对象属性设置(attr、access、$.attr)源代码分析

2012-11-02 16:04  VVG  阅读(2150)  评论(1编辑  收藏

jQuery中设置对象属性有以下几种:

1、获取属性attr(name)  

$("img").attr("src"); 

2、设置属性attr(name,value)

$("img").attr("src","test.jpg");

3、批量设置属性attr(properties)

$("img").attr({ src: "test.jpg", alt: "Test Image" });

4、为所有匹配的元素设置一个计算的属性值,由这个函数计算的值作为属性值。 attr(key, function(index, attr))

$("img").attr("title", function() { return this.src });
// 获取当前元素的src值作为title属性值

5、移除属性 removeAttr(name)

$("img").removeAttr("src");

其中都会用到jQuery对象的attr方法,attr的源代码如下:

jQuery.fn.extend({
     attr: function( name, value ) {
         return access( this, name, value, true, jQuery.attr );
     })

可以看到,attr只是起到一个传值的作用,然后返回的是access的返回值,再来看看access函数中的代码:

function access( elems, key, value, exec, fn, pass) {
     var length = elems.length;
     // 如果key是对象,则拆分成名值单独赋值
     if ( typeof key === "object" ) {
         for ( var k in key ) {
             access( elems, k, key[k], exec, fn, value );
         }
         return elems;
     }
     
     // 如果value含值,则给属性赋值
     if ( value !== undefined ) {
 
         // 如果value为函数的时候综合判断是否需要执行此函数
         // 判断VALUE是否为函数,是函数则exec为true
         exec = !pass && exec && jQuery.isFunction(value);
 
         // 拆分对象,单独赋值
         for ( var i = 0; i < length; i++ ) {
             // 对单独的对象调用jQuery.attr,设置属性
             // 如果value是函数则执行value.call( elems[i], i, fn( elems[i], key ) )
             fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass);
         }
         // 返回对象数组
         return elems;
     }
     
     // 如果上面的条件都不符合,length有值则获取属性值,无对象则为undefined
     return length ? fn( elems[0], key ) : undefined;
 }

access就像它单词的意思一样是一个入口,判断key、value值的不同类型,最终会把值传递给jQuery的静态方法attr处理,即($.attr()):

attr静态方法中分别对浏览器的兼容性、各种特殊属性做了相应的处理

jQuery.extend({
    attrFn: {
        val: true,
        css: true,
        html: true,
        text: true,
        data: true,
        width: true,
        height: true,
        offset: true
    },
        
    attr: function( elem, name, value, pass ) {
        // don't set attributes on text and comment nodes
        // 如果对象为空、文字、注释则返回undefined
        if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
            return undefined;
        }
        // 如果 name为 val、css、html、text、data、width、height、offset
        // 则直接调用jquery对应的方法如:$('p').html(value);
        if ( pass && name in jQuery.attrFn ) {
            return jQuery(elem)[name](value);
        }

        // 不是xml文档
        var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ),
            // Whether we are setting (or getting)
            // 是否需要设置或者获取
            set = value !== undefined;

        // Try to normalize/fix the name
        // 转换为适配的属性,如 class -> className
        name = notxml && jQuery.props[ name ] || name;

        // Only do all the following if this is a node (faster for style)
        // 元素element 对象
        if ( elem.nodeType === 1 ) {
            // These attributes require special treatment
            // rspecialurl = /href|src|style/
            var special = rspecialurl.test( name );

            // Safari mis-reports the default selected property of an option
            // Accessing the parent's selectedIndex property fixes it
            /*
            *  以下为opselected的bug修复
            *  http://www.cnblogs.com/GrayZhang/archive/2010/10/28/feature-detection-jquery1-4.html
            * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
             <select id="optSelected">
             </select>
             <script type="text/javascript">
             var select = document.getElementById('optSelected');
             var option = document.createElement('option');
             select.appendChild(option);
             console.log(option.selected);
             </script>
             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             以下为各浏览器中运行结果:

             浏览器      | 结果
             ----------- | -----
             IE6         | false
             IE7         | false
             IE8         | false
             IE9 beta    | false
             Firefox 3.6 | true
             Chrome 7    | true
             Safari 5    | false

             经测试,IE系列和Safari使用`appendChild`对空的`<select>`元素添加一个`<option>`后,该`<option>`的`selected`属性不会被默认设置为**true**。

             该问题引起的BUG描述如下:

             > 部分浏览器在获取option的selected属性时,会错误地返回false。

             该问题的解决方案是在访问`selected`属性时,先访问其父级`<select>`元素的`selectedIndex`属性,强迫浏览器计算`<option>`的`selected`属性,
             以得到正确的值。需要注意的是`<option>`元素的父元素不一定是`<select>`,也有可能是`<optgroup>`。具体代码如下:

             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
             if (!support.optSelected) {
             var parent = option.parentNode;
             parent.selectedIndex;
             //处理optgroup时的情况
             if (parent.parentNode) {
             parent.parentNode.selectedIndex;
             }
             }
             return option.selected;
             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
             */
            if ( name === "selected" && !jQuery.support.optSelected ) {
                var parent = elem.parentNode;
                if ( parent ) {
                    parent.selectedIndex;
    
                    // Make sure that it also works with optgroups, see #5701
                    if ( parent.parentNode ) {
                        parent.parentNode.selectedIndex;
                    }
                }
            }

            // If applicable, access the attribute via the DOM 0 way
            if ( name in elem && notxml && !special ) {
                if ( set ) {
                    // We can't allow the type property to be changed (since it causes problems in IE)
                    // rtype = /(button|input)/i,
                    // button 与 input 不允许修改 type属性
                    if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) {
                        jQuery.error( "type property can't be changed" );
                    }

                    elem[ name ] = value;
                }

                // browsers index elements by id/name on forms, give priority to attributes.
                // 元素为form
                if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) {
                    return elem.getAttributeNode( name ).nodeValue;
                }

                // 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/
                if ( name === "tabIndex" ) {
                    var attributeNode = elem.getAttributeNode( "tabIndex" );

                    return attributeNode && attributeNode.specified ?
                        attributeNode.value :
                        rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
                            0 :
                            undefined;
                }

                return elem[ name ];
            }

            if ( !jQuery.support.style && notxml && name === "style" ) {
                if ( set ) {
                    elem.style.cssText = "" + value;
                }

                return elem.style.cssText;
            }

            if ( set ) {
                // convert the value to a string (all browsers do this but IE) see #1070
                elem.setAttribute( name, "" + value );
            }

            var attr = !jQuery.support.hrefNormalized && notxml && special ?
                    // Some attributes require a special call on IE
                    elem.getAttribute( name, 2 ) :
                    elem.getAttribute( name );

            // Non-existent attributes return null, we normalize to undefined
            return attr === null ? undefined : attr;
        }

        // elem is actually elem.style ... set the style
        // Using attr for specific style information is now deprecated. Use style instead.
        return jQuery.style( elem, name, value );
    }
});