jQuery.val 如何能够绕过值 属性 上的 get/set 函数?

How is jQuery.val able to bypass the get/set functions on the value property?

const { get, set } = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, 'value');
let countryElement = $('select[name="country_id"]').get(0);
Object.defineProperty(countryElement, 'value', {
    get() {
        console.log('calling get');
        return get.call(this);
    },
    set(newVal) {
        console.log('New value assigned to selected[name="country_id"]: ' + newVal);
        return set.call(this, newVal);
    }
});

如果我运行 document.querySelector('select[name="country_id"]').value = 'CA' 则调用设置函数。如果我 运行 $('select[name="country_id"]').val('CA') 不会调用设置函数。

我的目标是观察由我无法更改的第三方 JS 工具执行的表单字段的更改。但是,我无法让它工作。当我深入研究这个问题时,我发现我什至可以 jQuery 来更改值而无需通过我的 set 函数。但我不明白这怎么可能。

查看 jQuery 的源代码。 val 确实如此,最终:

        hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];

        // If set returns undefined, fall back to normal setting
        if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
            this.value = val;
        }

所以它访问并调用了 jQuery.valHooks.select.set,在源代码的其他地方确实如此:

        set: function( elem, value ) {
            var optionSet, option,
                options = elem.options,
                values = jQuery.makeArray( value ),
                i = options.length;

            while ( i-- ) {
                option = options[ i ];

                /* eslint-disable no-cond-assign */

                if ( option.selected =
                    jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
                ) {
                    optionSet = true;
                }

                /* eslint-enable no-cond-assign */
            }

            // Force browsers to behave consistently when non-matching value is set
            if ( !optionSet ) {
                elem.selectedIndex = -1;
            }
            return values;
        }

它遍历选项。上面函数中唯一真正改变 DOM 的行是:

option.selected = jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1

elem.selectedIndex = -1;

所以,jQuery 根本没有调用值 setter。您可以模拟它在做什么,并通过分配给您想要 selected.

的选项的 selected 属性 来设置 <select> 的值

const { get, set } = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, 'value');
let countryElement = $('select[name="country_id"]').get(0);
Object.defineProperty(countryElement, 'value', {
    get() {
        console.log('calling get');
        return get.call(this);
    },
    set(newVal) {
        console.log('New value assigned to selected[name="country_id"]: ' + newVal);
        return set.call(this, newVal);
    }
});

countryElement.children[1].selected = true;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select name="country_id">
  <option>foo</option>
  <option value="CA">CA</option>
</select>

My goal is to observe changes to a form field which are executed by a third party JS tool which I cannot change.

有多种方法可以更改 <select> 值,其中包括 .value、设置选项的 .selected 属性 以及设置 [= select 的 23=]。您需要实施所有这些才能可靠地执行您想要的操作。