当JavaScript改变输入值时的事件?

35
当通过JavaScript代码更改元素的值时,触发的事件是什么?例如:
$input.value = 12;

input 事件在这里并没有起到作用,因为改变值的不是用户。

在 Chrome 上进行测试时,change 事件没有被触发。可能是因为该元素没有失去焦点(它没有获得焦点,因此也无法失去焦点)?


3
JavaScript更改值时不会触发任何事件。 - Barmar
@Kinduser 不... - epascarello
虽然有一些例外,但事件大多数情况下只会由影响浏览器的外部操作触发,而不是由Javascript代码触发。 - Barmar
“不”是完整的答案。 - epascarello
我似乎找不到一个非jQuery的重复问题,例如http://stackoverflow.com/questions/30291801/input-onchange-event-dont-fire-when-value-is-set-manually。 - epascarello
8个回答

19

这方面没有内置的事件。您至少有四个选择:

  1. 每次在代码中更改 $input.value 时,调用您想要触发的代码
  2. 轮询以检测更改
  3. 自己创建一个用于更改值的方法,该方法还将进行通知
  4. (第三种方法的变体) 自己创建一个 属性 用于更改值,该属性还将进行通知

需要注意的是,在上述四种方法中,#1、#3 和 #4 都需要您从简单的 $input.value = "new value"; 这样的代码中做出一些更改。轮询(选项 #2)是唯一适用于直接设置 value 的代码的方法。

细节:

  1. The simplest solution: Any time you change $input.value in code, call the code you want triggered by the change:

    $input.value = "new value";
    handleValueChange();
    
  2. Poll for changes:

    var last$inputValue = $input.value;
    setInterval(function() {
        var newValue = $input.value;
        if (last$inputValue != newValue) {
            last$inputValue = newValue;
            handleValueChange();
        }
    }, 50); // 20 times/second
    

    Polling has a bad reputation (for good reasons), because it's a constant CPU consumer. Modern browsers dial down timer events (or even bring them to a stop) when the tab doesn't have focus, which mitigates that a bit. 20 times/second isn't an issue on modern systems, even mobiles.

    But still, polling is an ugly last resort.

    Example:

    var $input = document.getElementById("$input");
    var last$inputValue = $input.value;
    setInterval(function() {
        var newValue = $input.value;
        if (last$inputValue != newValue) {
            last$inputValue = newValue;
            handleValueChange();
        }
    }, 50); // 20 times/second
    function handleValueChange() {
        console.log("$input's value changed: " + $input.value);
    }
    // Trigger a change
    setTimeout(function() {
        $input.value = "new value";
    }, 800);
    <input type="text" id="$input">

  3. Give yourself a function to set the value and notify you, and use that function instead of value, combined with an input event handler to catch changes by users:

    $input.setValue = function(newValue) {
        this.value = newValue;
        handleValueChange();
    };
    $input.addEventListener("input", handleValueChange, false);
    

    Usage:

    $input.setValue("new value");
    

    Naturally, you have to remember to use setValue instead of assigning to value.

    Example:

    var $input = document.getElementById("$input");
    $input.setValue = function(newValue) {
        this.value = newValue;
        handleValueChange();
    };
    $input.addEventListener("input", handleValueChange, false);
    function handleValueChange() {
        console.log("$input's value changed: " + $input.value);
    }
    // Trigger a change
    setTimeout(function() {
        $input.setValue("new value");
    }, 800);
    <input type="text" id="$input">

  4. A variant on #3: Give yourself a different property you can set (again combined with an event handler for user changes):

    Object.defineProperty($input, "val", {
        get: function() {
            return this.value;
        },
        set: function(newValue) {
            this.value = newValue;
            handleValueChange();
        }
    });
    $input.addEventListener("input", handleValueChange, false);
    

    Usage:

    $input.val = "new value";
    

    This works in all modern browsers, even old Android, and even IE8 (which supports defineProperty on DOM elements, but not JavaScript objects in general). Of course, you'd need to test it on your target browsers.

    But $input.val = ... looks like an error to anyone used to reading normal DOM code (or jQuery code).

    Before you ask: No, you can't use the above to replace the value property itself.

    Example:

    var $input = document.getElementById("$input");
    Object.defineProperty($input, "val", {
        get: function() {
            return this.value;
        },
        set: function(newValue) {
            this.value = newValue;
            handleValueChange();
        }
    });
    $input.addEventListener("input", handleValueChange, false);
    function handleValueChange() {
        console.log("$input's value changed: " + $input.value);
    }
    // Trigger a change
    setTimeout(function() {
        $input.val = "new value";
    }, 800);
    <input type="text" id="$input">


13

根据 @t-j-crowder 和 @maciej-swist 的回答,让我们添加这个使用 ".apply" 函数的解决方案,它可以防止无限循环而不需要重新定义对象。

 function customInputSetter(){

  var descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");
  var originalSet = descriptor.set;

  // define our own setter
  descriptor.set = function(val) {
    console.log("Value set", this, val);
    originalSet.apply(this,arguments);
  }

  Object.defineProperty(HTMLInputElement.prototype, "value", descriptor);
}

7
我倾向于采纳 T.J. Crowder 的建议,给出第五个选项。 但是,不需要添加新的属性,而是可以通过更改现有的“value”属性来触发设置时的其他操作 - 无论是针对特定输入元素还是所有输入对象:
//First store the initial descriptor of the "value" property:

var descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");
var inputSetter = descriptor.set;

//Then modify the "setter" of the value to notify when the value is changed:

descriptor.set = function(val) {

    //changing to native setter to prevent the loop while setting the value
    Object.defineProperty(this, "value", {set:inputSetter});
    this.value = val;

    //Custom code triggered when $input.value is set
    console.log("Value set: "+val);

    //changing back to custom setter
    Object.defineProperty(this, "value", descriptor);   
}

//Last add the new "value" descriptor to the $input element
Object.defineProperty($input, "value", descriptor);

可以不针对特定的输入元素更改“value”属性,而是可以通用地更改所有输入元素的值:

Object.defineProperty(HTMLInputElement.prototype, "value", descriptor);

这种方法仅适用于使用JavaScript更改值的情况,例如input.value="新值"。当在输入框中输入新值时,它不起作用。


6
以下是针对所有输入框的值属性更改的解决方案:
var valueDescriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");

HTMLInputElement.prototype.addInputChangedByJsListener = function(cb) {
    if(!this.hasOwnProperty("_inputChangedByJSListeners")) {
        this._inputChangedByJSListeners = [];
    }
    this._inputChangedByJSListeners.push(cb);
}

Object.defineProperty(HTMLInputElement.prototype, "value", {
    get: function() {
        return valueDescriptor.get.apply(this, arguments);
    },
    set: function() {
        var self = this;
        valueDescriptor.set.apply(self, arguments);
        if(this.hasOwnProperty("_inputChangedByJSListeners")){
            this._inputChangedByJSListeners.forEach(function(cb) {
                cb.apply(self);
            })
        }
    }
});

使用示例:

document.getElementById("myInput").addInputChangedByJsListener(function() {
    console.log("Input changed to \"" + this.value + "\"");
});

聪明而巧妙,我喜欢它,只要在调试时使用即可 :) - CertainPerformance

5

一种可能的策略是使用MutationObserver检测属性的变化,如下所示:

var observer = new MutationObserver(function(mutations) {
          mutations.forEach(function(){
                 console.log('hello')});
          });

          observer.observe($input, {
                 attributes: true
          });

尽管这本身不能检测到像下面这样的更改:
$input.value = 12

它将检测实际值属性的更改:

$input.setAttribute('value', 12)

如果您是通过编程设置值的,只需确保在value = 12语句旁边更改属性,就可以获得所需的结果。


那是一个非常花哨的解决方案。实际上,我需要反映外部变化,所以这并没有帮助我。但它仍然是一个非常好的解决方案。 - pery mimon
这在Chrome和Firefox上都可以工作。它们似乎也会更改DOM中的值! - David Vielhuber

4

一种简单的方法是在值改变时触发一个输入事件。

你可以使用纯 JavaScript 实现此操作。

在脚本的早期,添加以下内容:

let inputEvent = new Event('input',{bubbles:true,cancelable: true});

你可以将“input”更改为你想要的事件,“change”,“blur”等。
然后,只需在相同元素上调用此事件即可在任何时候更改值。
input.value = 12;// <- your example
input.dispatchEvent(inputEvent);// <- calling an event

这是普通的JavaScript。

重点是第三方更改了值,而不是我,但感谢您的建议。 - pery mimon

1

JavaScript修改输入值时触发事件?

我们可以使用事件触发和target.dispatchEvent来实现,详情请参见此question

以下是文本输入框的示例:

const input = document.querySelector('.myTextInput'); 
    
//Create the appropriate event according to needs
const change = new InputEvent('change');
  
//Change the input value
input.value = 'new value';

//Fire the event;
const isNotCancelled = input.dispatchEvent(change);

当事件类型上的任何处理程序不使用 event.preventDefault() 时,isNotCancelled 将为 true,否则为 false

那个好的答案可以与正确的答案结合起来。即使输入是由代码更改的,也可以触发输入事件。单独使用它并不能回答问题,因为事件不会自己触发,无论值何时何地更改(可能是由第三方代码更改)。 - pery mimon

1
我认为没有一个事件可以涵盖所有针对输入的编程场景。但是,我可以告诉您,您可以编写程序“触发”事件(自定义事件、常规事件或仅触发事件处理程序)。
在我看来,您正在使用jQuery,因此,您可以使用以下代码:
$('input#inputId').val('my new value...').triggerHandler('change');

在这个示例中,您正在分配一个值,并强制调用与“change”事件绑定的处理程序(或处理程序)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接