使用 vanilla javascript 和 MutationObserver 检测输入标签中的值变化

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32383349/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 15:11:41  来源:igfitidea点击:

detect value change in input tag with vanilla javascript and MutationObserver

javascripthtmlmutation-observers

提问by while true

I want to detect when text/value change in input field. Even if I change the value with js, I want to detect that changes.

我想检测输入字段中的文本/值何时更改。即使我用 js 更改了值,我也想检测该更改。

Here's what I've tried so far in demo in fiddle.

这是我迄今为止在 fiddle 的演示中尝试过的。

HTML:

HTML:

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

JavaScript:

JavaScript:

var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    // console.log('Mutation type: ' + mutation.type);
    if ( mutation.type == 'childList' ) {
      if (mutation.addedNodes.length >= 1) {
        if (mutation.addedNodes[0].nodeName != '#text') {
           // console.log('Added ' + mutation.addedNodes[0].tagName + ' tag.');
        }
      }
      else if (mutation.removedNodes.length >= 1) {
         // console.log('Removed ' + mutation.removedNodes[0].tagName + ' tag.')
      }
    }
     if (mutation.type == 'attributes') {
      console.log('Modified ' + mutation.attributeName + ' attribute.')
    }
  });   
});

var observerConfig = {
        attributes: true,
        childList: false,
        characterData: false
};

// Listen to all changes to body and child nodes
var targetNode = document.getElementById("exNumber");
observer.observe(targetNode, observerConfig);

回答by David

To understand what is going on is necessary to clear up the difference between attribute(content attribute) and property(IDL attribute). I won't expand on this as in SO there are already excellent answers covering the topic:

要了解正在发生的事情,有必要弄清楚属性(内容属性)和属性(IDL 属性)之间的区别。我不会对此进行扩展,因为在 SO 中已经有很好的答案涵盖了该主题:

When you change the content of a inputelement, by typing in or by JS:

当您input通过输入或通过 JS更改元素的内容时:

targetNode.value="foo";

the browser updates the valuepropertybut not the valueattribute(which reflects the defaultValueproperty instead).

浏览器更新value属性而不是value属性(而是反映defaultValue属性)。

Then, if we look at the spec of MutationObserver, we will see that attributesis one of the object members that can be used. So if you explicitly set the valueattribute:

然后,如果我们查看MutationObserver规范,我们将看到属性是可以使用的对象成员之一。因此,如果您明确设置value属性:

targetNode.setAttribute("value", "foo");

MutationObserver will notify an attribute modification. But there is nothing like propertiesin the list of the spec: the valueproperty can not be observed.

MutationObserver 将通知属性修改。但是规范列表中没有类似的属性:该value属性无法被观察到

If you want to detect when an user alters the content of your input element, the inputeventis the most straightforward way. If you need to catch JS modifications, go for setIntervaland compare the new value with the old one.

如果您想检测用户何时更改了输入元素的内容,则input事件是最直接的方式。如果您需要捕获 JS 修改,请查找setInterval并将新值与旧值进行比较。

Check this SO questionto know about different alternatives and its limitations.

检查此SO 问题以了解不同的替代方案及其局限性。

回答by GracefulLight

the value property can be observed, Don't waste your time.

可以观察到value 属性,不要浪费时间。

function changeValue (event, target) {
    document.querySelector("#" + target).value = new Date().getTime();
}
 
function changeContentValue () {
    document.querySelector("#content").value = new Date().getTime();
}
 
Object.defineProperty(document.querySelector("#content"), "value", {
    set:  function (t) {
        alert('#changed content value');
        var caller = arguments.callee
            ? (arguments.callee.caller ? arguments.callee.caller : arguments.callee)
            : ''
 
        console.log('this =>', this);
        console.log('event => ', event || window.event);
        console.log('caller => ', caller);
        return this.textContent = t;
    }
});
<form id="form" name="form" action="test.php" method="post">
        <input id="writer" type="text" name="writer" value="" placeholder="writer" /> <br />
        <textarea id="content" name="content" placeholder="content" ></textarea> <br />
        <button type="button" >Submit (no action)</button>
</form>
<button type="button" onClick="changeValue(this, 'content')">Change Content</button>

回答by Shawn Regan

This works and preserves and chains the original setter and getter so everything else about your field still works.

这有效并保留和链接原始的 setter 和 getter,因此有关您的领域的所有其他内容仍然有效。

var registered = [];
var setDetectChangeHandler = function(field) {
  if (!registered.includes(field)) {
    var superProps = Object.getPrototypeOf(field);
    var superSet = Object.getOwnPropertyDescriptor(superProps, "value").set;
    var superGet = Object.getOwnPropertyDescriptor(superProps, "value").get;
    var newProps = {
      get: function() {
        return superGet.apply(this, arguments);
      },
      set: function (t) {
        var _this = this;
        setTimeout( function() { _this.dispatchEvent(new Event("change")); }, 50);
        return superSet.apply(this, arguments);
      }
    };
    Object.defineProperty(field, "value", newProps);
    registered.push(field);
  }
}

回答by akinuri

I've modified Shawn's methoda little and wanted to share it. Can't believe there's actually a solution to this.

我稍微修改了Shawn 的方法,想分享一下。无法相信实际上有解决方案。

Type into the input box to see the default behavior. Now, open the DevTools and select the input element, then change its value, e.g. $0.value = "hello". Examine the UI vs. API difference. It seems UI interactions do not modify valueproperty directly. If it were, it would also log "...changed via API...".

在输入框中键入以查看默认行为。现在,打开 DevTools 并选择 input 元素,然后更改其值,例如$0.value = "hello". 检查 UI 与 API 的区别。似乎 UI 交互不value直接修改属性。如果是,它也会记录"...changed via API...".

let inputBox = document.querySelector("#inputBox");

inputBox.addEventListener("input", function () {
    console.log("Input value changed via UI. New value: '%s'", this.value);
});

observeElement(inputBox, "value", function (oldValue, newValue) {
    console.log("Input value changed via API. Value changed from '%s' to '%s'", oldValue, newValue);
});

function observeElement(element, property, callback, delay = 0) {
    let elementPrototype = Object.getPrototypeOf(element);
    if (elementPrototype.hasOwnProperty(property)) {
        let descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
        Object.defineProperty(element, property, {
            get: function() {
                return descriptor.get.apply(this, arguments);
            },
            set: function () {
                let oldValue = this[property];
                descriptor.set.apply(this, arguments);
                let newValue = this[property];
                if (typeof callback == "function") {
                    setTimeout(callback.bind(this, oldValue, newValue), delay);
                }
                return newValue;
            }
        });
    }
}
<input type="text" id="inputBox" placeholder="Enter something" />