JavaScript动态的改变input的value属性时,如何自动触发oninput、onchange事件?

默凡
  • 1.2k

如页面上有如下input:

<input type="text" id="demo"/>

给input加上事件:

var input = document.getElementById('demo');
input.addEventListener('oninput', function(){
    console.log('oninput event ');
}, false);

input.addEventListener('onchange', function(){
    console.log('onchange event');
}, false);

现在我用JavaScript动态的设置input的值:

document.getElementById('demo').value = 'value change';

会发现oninput和onchange事件都没有自动触发,但输入框的值却已经变化了。如果想要触发事件,则必须手动触发才行。

要想设置value值得时候,自动触发一些事件有没有对应的解决办法呢?

回复
阅读 47.2k
15 个回答

lerte 说的对,数据双向绑定,不过不用框架也行,只要知道原理;楼主的问题实际上是:

改变了一个对象的属性,怎么去自动触发一个程序,或者访问一个属性,怎么去自动触发一个程序

实际上,可以新建另一个访问器属性 _value ,把原来 js 对 value 的操作改为对 _value 属性的操作,这样访问 _value 时,会自动调用 get 函数获取 value 的值,设置 _value 属性时,会自动调用 set 函数设置 value 属性的值,你可以将你的程序绑定在 get/set 函数中,这样就可以监听 js 修改 value 数据的变化;
如果直接设置 value 属性为 访问器属性,结果会造成,用户在页面修改了 value,不会调用 set 函数,所以 想了上面的中介 _value,此处感谢@叫我小蓝就行了 的提醒。
题主的案例:
js:

window.onload = function() {

    let input = document.getElementById('demo');

    function oninputCallback() {
        console.log('oninput event ');
    }

    function onchangeCallback() {
        console.log('onchange event');
    }

    input.addEventListener('oninput', oninputCallback, false);
    input.addEventListener('onchange', onchangeCallback, false);

    Object.defineProperty(input, '_value', {
        configurable: true,
        set: function(value) {
            this.value = value;
            oninputCallback();
            onchangeCallback();
        },
        get: function() {
            return this.value;
        }
    });
};

html:

<body>
  <input type="text" id="demo"/>
</body>

效果

clipboard.png

再给个例子,根据数据类型的变化,自动修改类名,demo

js:

window.onload = function() {
let bonusDiv = document.getElementById('bonusDiv');

Object.defineProperty(bonusDiv, 'data-type', {
    configurable: true,
    set: function(value) {
        switch (value) {
            case 'x1':
                this.className = 'red';
                break;
            case 'x2':
                this.className = 'blue';
                break;
            case 'x3':
                this.className = 'purple';
                break;
        }
    }
});
};

html:

    <div id="bonusDiv" ></div>

效果:

clipboard.png

【Stack Overflow】Event when input value is changed by JavaScript?

监听 js 动态 修改 input value 事件

方案1

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

customInputSetter();

方案2
Here is a solution to hook the value property changed for all inputs:

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

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

方案3

/**
 * [changeValueListener 监听 js 修改 input 和 textarea 的 value。]
 * @param  {[HTMLElement]}   element  [具有 value 属性的 html 元素,如 input 和 textarea。]
 * @param  {Function} callback [回调,this 为 element,参数为当前值。]
 * @return {[HTMLElement]}            [element]
 */
function changeValueListener(element, callback) {
    if (!Array.isArray(element.callbacks)) {
        element.callbacks = [];
        var valueDes = Object.getOwnPropertyDescriptor(element.constructor.prototype, "value");
        Object.defineProperty(element, 'value', {
            set: function(v) {
                // console.log('set', v, this, arguments);
                element.callbacks.forEach(function(callback) {
                    callback.call(this, v);
                });
                valueDes.set.apply(this, arguments);
            },
            get: function() {
                // console.log('get', this, arguments);
                return valueDes.get.apply(this, arguments);
            }
        });
    }
    element.callbacks.push(callback);
    return element;
}

// 调用
var input = document.getElementById('foo');
changeValueListener(input, function(v) {
    console.log('a callback', this, v);
});
// 支持绑定多个回调
changeValueListener(input, function(v) {
    console.log('b callback', this, v);
});

我觉得数据双向绑定蛮好的,不过还有别的解决办法,可以用js的事件构造器,让js触发你想要的事件

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <input type="text" id="demo" />
    <!-- Simulate click -->
    <input type="button" onclick="changeValue();" value="点击我改变input值" />
    <!-- Add a click handler that calls preventDefault -->
    <input type="button" onclick="addHandler();" value="js触发input和change默认事件" />
    <!-- Remove the click handler that calls preventDefault -->
    <input type="button" onclick="removeHandler();" value="js取消触发input和change默认事件" />
    <script>
        var input = document.getElementById('demo');
        input.addEventListener('input', function() {
            console.log('oninput event ');
        }, true);


        input.addEventListener('change', function() {
            console.log('onchange event');
        }, false);

        // 参考网站
        // https://stackoverflow.com/questions/2490825/how-to-trigger-event-in-javascript
        // https://developer.mozilla.org/zh-CN/docs/Web/Guide/Events/Creating_and_triggering_events

        function changeValue() { //改变input的值函数
            input.value = Math.random() * 10;
            simulateEvent("demo", "change");
            simulateEvent("demo", "input");
        }

        function simulateEvent(id, event) { //js触发事件--事件构造器
            var evt = document.createEvent("Event");
            evt.initEvent(event, true, true);
            var cb = document.getElementById(id);
            var canceled = !cb.dispatchEvent(evt);
            if (canceled) {
                // A handler called preventDefault
                // console.log("canceled");
            } else {
                // None of the handlers called preventDefault
                // console.log("not canceled");
            }
        }

        function preventDef(event) {
            event.preventDefault();
        }

        function addHandler() {
            document.getElementById("demo").addEventListener("change", preventDef, false);
            document.getElementById("demo").addEventListener("input", preventDef, false);
        }

        function removeHandler() {
            document.getElementById("demo").removeEventListener("change", preventDef, false);
            document.getElementById("demo").removeEventListener("input", preventDef, false);
        }
    </script>
</body>

</html>

js动态改变时,手动触发input事件

其实,仔细一想,要是JavaScript可以通过设置值来触发input事件,本身好像就不太合理。
因为当我打开一个页面的时候,页面中某一段js可以通过某种方法来触发需要用户手动触发的事件,也就是相当于在用户不知情的情况下,替用户做了某些操作(填写表单、填写密码),感觉不太安全。

onchange 只会在你离开(blur) input 的时候触发。你可以将回调函数抽取出来,在 JS 改变 input 时同时调用回调。或者用 oninput ,搭配 onpropertychange 可兼容 IE8 。

oninput 事件在value 改变时触发,是实时的,然而通过 js 改变 value 时,却不会触发。onpropertychange用js改变是可以触发的,单限于ie。可以封装一下

        var input = document.getElementById('demo');
        valuechange(input,function(){
            input.value = 'value change';
            return true;
        },function(){
            console.log('this input change');
        }); 
function valuechange(obj,myevent,fn){
    var ie = !!window.ActiveXObject;  
    if(ie){  
        if(myevent()){
            obj.onpropertychange = fn;  
        }         
    }else{  
        if(myevent()){
            fn();
        }       
    }
}

你可以做一个定时器,设置一些标识变量,在定时器里面根据这些变量达到你的效果

var onchange = function()
{
    console.log('onchange event');
};
input.addEventListener('onchange', function(){
    onchange();
}, false);

document.getElementById('demo').value = 'value change';
onchange();//你都用程序改值了,当然是用程序调用change啊

程序无法获取页面操作才有了事件监听,比如用户点击按钮如果没有监听点击事件,程序不知道用户是否点击按钮,于是用addEventListener监听。
如果程序知道操作干嘛要用事件监听多此一举??

用Vue或者其他框架,做数据双向绑定

$("#demo").trigger("oninput").trigger("onchange");

胡来
  • 2
新手上路,请多包涵

遇到同样的问题。楼主怎么解决的

你既然都是用js去操作input了,这些事件触发的处理(调用),你写到js后续操作中就完了,为什么还要想着触发事件?

可以将监听函数内部的操作作为一个函数,当改变input的值时,调用这个函数。

wqh0109663
  • 2
新手上路,请多包涵

貌似被采纳的答案不支持在ie中运行 我在Stack Overflow上问过相同的问题。

宣传栏