jQuery 中 attr() 和 prop() 方法的區別

前幾天,有人給 Multiple Select 插件 提了問題:javascript

setSelects doesn't work in Firefox when using jquery 1.9.0php

一直都在用 jQuery 1.8.3 的版本,沒有嘗試過 jQuery 1.9.0 的版本。java

因而,開始調試代碼,在 1.9.0 的版本中:node

<input type="checkbox" />
<script> $(function() { $('input').click(function() { $(this).attr('checked'); }); }); </script>

點擊 checkbox,結果都是 undefinedjquery

而在 1.8.3 的版本中,結果是 checked 和 undefinedgit

到這裏,問題答案找到了,就是使用 attr() 方法的問題,因而查看官方文檔, 才知道從 jQuery 1.6 開始新增了一個方法 prop(),可是一直都沒有使用過。github

從中文意思看,二者分別是獲取/設置 attributes 和 properties 的方法,那麼爲何還要增長 prop() 方法呢?web

Before jQuery 1.6, the .attr() method sometimes took property values into account when retrieving some attributes, which could cause inconsistent behavior.

由於在 jQuery 1.6 以前,使用 attr() 有時候會出現不一致的行爲。fetch

那麼,何時使用attr(),何時使用prop()?this

To retrieve and change DOM properties such as the checked, selected, or disabled state of form elements, use the .prop() method.

根據官方的建議:具備 true 和 false 兩個屬性的屬性,如 checked, selected 或者 disabled 使用prop(),其餘的使用 attr()

到此,將 attr('checked') 改爲 prop('checked') 便可修復提的 issues 了。

^_^

等等,貌似問題還沒真正解決,爲何開頭例子中 jQuery 1.8.3 和 1.9.0 使用 attr() 會有所區別呢?

想知道他們的區別,最好的辦法仍是看他們的源代碼:

1.8.3 attr():

attr: function( elem, name, value, pass ) {
    var ret, hooks, notxml,
        nType = elem.nodeType;

    // don't get/set attributes on text, comment and attribute nodes
    if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
        return;
    }

    if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) {
        return jQuery( elem )[ name ]( value );
    }

    // Fallback to prop when attributes are not supported
    if ( typeof elem.getAttribute === "undefined" ) {
        return jQuery.prop( elem, name, value );
    }

    notxml = nType !== 1 || !jQuery.isXMLDoc( elem );

    // All attributes are lowercase
    // Grab necessary hook if one is defined
    if ( notxml ) {
        name = name.toLowerCase();
        hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
    }

    if ( value !== undefined ) {

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

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

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

1.9.0 attr():

attr: function( elem, name, value ) {
    var ret, hooks, notxml,
        nType = elem.nodeType;

    // don't get/set attributes on text, comment and attribute nodes
    if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
        return;
    }

    // Fallback to prop when attributes are not supported
    if ( typeof elem.getAttribute === "undefined" ) {
        return jQuery.prop( elem, name, value );
    }

    notxml = nType !== 1 || !jQuery.isXMLDoc( elem );

    // All attributes are lowercase
    // Grab necessary hook if one is defined
    if ( notxml ) {
        name = name.toLowerCase();
        hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
    }

    if ( value !== undefined ) {

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

        } else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
            return ret;

        } else {
            elem.setAttribute( name, value + "" );
            return value;
        }

    } else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
        return ret;

    } else {

        // In IE9+, Flash objects don't have .getAttribute (#12945)
        // Support: IE9+
        if ( typeof elem.getAttribute !== "undefined" ) {
            ret =  elem.getAttribute( name );
        }

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

1.8.3 和 1.9.0 的 prop() 是同樣的:

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

    // don't get/set properties on text, comment and attribute nodes
    if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
        return;
    }

    notxml = nType !== 1 || !jQuery.isXMLDoc( elem );

    if ( notxml ) {
        // Fix name and attach hooks
        name = jQuery.propFix[ name ] || name;
        hooks = jQuery.propHooks[ name ];
    }

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

首先,咱們看下 attr() 和 prop() 的區別

attr() 裏面,最關鍵的兩行代碼

elem.setAttribute( name, value + "" ); 

ret =  elem.getAttribute( name );

很明顯的看出來,使用的 DOM 的 API setAttribute() 和 getAttribute() 方法操做的屬性元素節點。

prop() 裏面,最關鍵的兩行代碼

return ( elem[ name ] = value );

return elem[ name ];

能夠理解爲 document.getElementById(el)[name] = value,這是轉化成 element 的一個屬性。

對比調試 1.9.0 和 1.8.3 的 attr() 方法,發現二者的區別在於

hooks.get( elem, name ))

返回的值不同,具體的實現:

1.8.3 中

boolHook = {
    get: function( elem, name ) {
        // Align boolean attributes with corresponding properties
        // Fall back to attribute presence where some booleans are not supported
        var attrNode,
            property = jQuery.prop( elem, name );
        return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
            name.toLowerCase() :
            undefined;
    }
}

1.9.0 中

boolHook = {
    get: function( elem, name ) {
        var
            // Use .prop to determine if this attribute is understood as boolean
            prop = jQuery.prop( elem, name ),

            // Fetch it accordingly
            attr = typeof prop === "boolean" && elem.getAttribute( name ),
            detail = typeof prop === "boolean" ?

                getSetInput && getSetAttribute ?
                    attr != null :
                    // oldIE fabricates an empty string for missing boolean attributes
                    // and conflates checked/selected into attroperties
                    ruseDefault.test( name ) ?
                        elem[ jQuery.camelCase( "default-" + name ) ] :
                        !!attr :

                // fetch an attribute node for properties not recognized as boolean
                elem.getAttributeNode( name );

        return detail && detail.value !== false ?
            name.toLowerCase() :
            undefined;
    }
}

因而可知,1.9.0 開始不建議使用 attr() 來對具備 true 和 false 兩個屬性的屬性進行操做了。

那麼咱們的結論是:

具備 true 和 false 兩個屬性的屬性,如 checked, selected 或者 disabled 使用prop(),其餘的使用 attr(),具體見下表:


注:本文中的大部分觀點以及例子屬於我的理解,不免還有不許確的地方,歡迎有相關研究的同行指正。

相關文章
相關標籤/搜索