捕获HTML输入元素的值的所有更改的现代方法是什么?

时间:2019-03-25 06:11:19

标签: javascript html

是否有一种标准方法来捕获HTML输入元素的值的所有更改,而不管它是由用户输入更改还是通过程序更改?

考虑以下代码(仅出于示例目的,并且未采用良好的编码习惯编写,您可以将其视为恰好能够在某些Web浏览器中运行的伪代码:P)

<!DOCTYPE html>
<html>
<head>
    <title>Test Page</title>
    <script>
        window.onload = () => {
            var test = document.getElementById("test");

            test.onchange = () => {
                console.log("OnChange Called");
                console.log(test.value);
            }  // Called when the input element loses focus and its value changed

            test.oninput = () => {
                console.log("OnInput Called");
                console.log(test.value);
            }  // Called whenever the input value changes

            test.onkeyup = () => {
                console.log("OnKeyUp Called");
                console.log(test.value);
            }  // some pre-HTML5 way of getting real-time input value changes
        }
    </script>
</head>
<body>
    <input type="text" name="test" id="test">
</body>
</html>

但是,当以编程方式更改输入元素的值时,这些事件都不会触发,就像有人在做

document.getElementById("test").value = "Hello there!!";

要捕获以编程方式更改的值,通常可以在过去完成以下两项操作之一:

1)告诉编码人员每次以编程方式更改输入值时手动触发onchange事件,例如

document.getElementById("test").value = "Hello there!!";
document.getElementById("test").onchange();

但是对于手头的这个项目,客户不会接受这种解决方案,因为他们有很多来来去去的承包商/分包商,我想他们只是不信任他们的承包商严格遵守这种规则,更重要的是,他们从以前的合同中获得了一个可行的解决方案,这是做事的第二种老方法

2)设置一个计时器,该计时器定期检查输入元素的值,并在每次更改时调用一个函数,诸如此类

        var pre_value = test.value;

        setInterval(() => {
            if (test.value !== pre_value) {
                console.log("Value changed");
                console.log(test.value);
                pre_value = test.value;
            }
        }, 200);  // checks the value every 200ms and see if it's changed

这看起来像是jQuery v1.6时代的某种恐龙,由于各种原因恕我直言,这是相当糟糕的,但是可以满足客户的需求。

现在我们到了2019年,我想知道是否有某种现代的方法可以代替上述代码? JavaScript的setter / getter似乎很有希望,但是当我尝试以下代码时,它只是破坏了HTML输入元素

        Object.defineProperty(test, "value", {
            set: v => {
                this.value = v;
                console.log("Setter called");
                console.log(test.value);
            },
            get: ()=> {
                console.log("Getter called");
                return this.value;
            }
        });

以编程方式分配test.value时将调用setter函数,但HTML页面上的input元素将以某种方式被破坏。

那么关于如何捕获HTML输入元素的值的所有更改并调用处理程序函数(不是古老的“使用轮询计时器”方法)的任何想法吗?

注意:请注意,此处的所有代码仅用于示例目的,不应在实际系统中使用,在实际系统中,最好使用addEventListener / attachEvent / dispatchEvent / fireEvent等方法。

2 个答案:

答案 0 :(得分:2)

要观察元素.value的分配和检索,可以使用Object.defineProperty,但是您需要在内部调用原始 setter和getter函数。自定义方法,这些方法可在HTMLInputElement.prototype上使用:

const { getAttribute, setAttribute } = test;
test.setAttribute = function(...args) {
  if (args[0] === 'value') {
    console.log('Setting value');
  }
  return setAttribute.apply(this, args);
};
test.getAttribute = function(...args) {
  if (args[0] === 'value') {
    console.log('Getting value');
  }
  return getAttribute.apply(this, args);
};



test.setAttribute('value', 'foo');
console.log(test.getAttribute('value'));
<input type="text" name="test" id="test">

请注意使用方法而不是箭头功能-这很重要,它可以保留this上下文。 (您也可以使用类似set.call(input, v)的方法,但这不太灵活)

仅用于更改.value。您可以根据需要为setAttribute('value进行类似的修补:

const { setAttribute } = test;
test.setAttribute = function(...args) {
  if (args[0] === 'value') {
    console.log('attribute set!');
  }
  return setAttribute.apply(this, args);
};

test.setAttribute('value', 'foo');
<input type="text" name="test" id="test">

答案 1 :(得分:1)

标准方法是在以编程方式更改值时不触发更改事件。
不仅有太多方法可以通过编程方式设置输入的值,而且,这只是对无限循环的要求。

如果在任何回调中设置了您的输入值,那么您将使页面崩溃。

想尝试吗?

<add key="Telerik.Web.UI.StyleSheetFolders" value="~/App_Themes/;" />
let called = 0; // to avoid blocking this page
const { set, get } = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
Object.defineProperty(inp, 'value', {
  set(v) {
    const ret = set.call(this, v);
    if(++called < 20) // I limit it to 20 calls to not kill this page
      this.dispatchEvent(new Event('all-changes'));
    return ret;
  },
  get() { return get.call(this); }
});

inp.addEventListener('all-changes', e => {
  inp.value = inp.value.toUpperCase();
  console.log('changed');
});

btn.onclick = e => inp.value = 'foo';

因此,最好的办法仍然是仅直接从负责更改的代码中调用任何回调。