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


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

setSelects doesn't work in Firefox when using jquery 1.9.0

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

於是,開始調試代碼,在 1.9.0 的版本中:

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

點擊 checkbox,結果都是 undefined

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

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

從中文意思看,兩者分別是獲取/設置 attributes 和 properties 的方法,那么為什么還要增加 prop() 方法呢?

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() 有時候會出現不一致的行為。

那么,什么時候使用attr(),什么時候使用prop()?

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(),具體見下表:


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM