使用 MutationObserver 检测输入值变化

IT技术 javascript html mutation-observers
2021-02-14 18:22:06

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

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

HTML :

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

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);
4个回答

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

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

targetNode.value="foo";

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

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

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

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

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

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

tnx david 所以唯一丑陋的解决方案是使用计时器。我不想那样做,这就是我尝试观察者的原因。但不幸的是,可能没有其他方法可以做到这一点
2021-04-15 18:22:06
在这种情况下,请检查另一个
2021-04-18 18:22:06
@Mene 感谢您指出这一点:这是错误的。我想我想说 HTML(定义属性)而不是 DOM(定义属性)。通常将属性的更改称为 HTML 中的更改。无论如何,严格来说,HTML 是驻留在服务器中的静态文本(它不会改变),而 DOM 是解析标记(每当用户或脚本修改它时它就会改变)产生的内存中对象,所以我刚刚删除了那句话。实际上,我重写了整个内容以使其更具体。
2021-04-19 18:22:06
onkeyup对我的情况没有用。实际上,由于某种原因,我无法修改外部脚本。输入字段的脚本更改值。所以我想检测文本何时更改
2021-04-22 18:22:06
你有关于为什么 dom 没有改变的任何细节吗?毕竟我可以在浏览器窗口中看到变化。
2021-04-26 18:22:06

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

在输入框中键入以查看默认行为。现在,打开 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" />

可以观察到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>

您能否提供有关您的代码如何工作的更多详细信息?
2021-04-26 18:22:06
我正在研究一个类似的问题,我对这个答案很感兴趣,但我有点挣扎。这仅适用于 textarea 的权利?由于您设置了出现在 textarea 字段内的 textContent,但 textContent 不会出现在 HTML 的输入字段内。
2021-05-03 18:22:06
@Josh输入字段的用途return this.defaultValue = t;:)
2021-05-05 18:22:06

这有效并保留和链接原始的 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);
  }
}
我喜欢这个解决方案。尽管这在特定情况下会导致一个小问题。我在具有去抖动行为的字段中添加了一个事件侦听器(用于输入事件)。我输入到该字段的值在 500 毫秒后更新(更正货币格式)。所以我更新字段,字段更新自身(它的值)。如果我使用这个 ( setDetectChangeHandler) 方法,我会得到一个循环。"input"事件连续触发。因此set,我没有在 中触发事件,而是将回调传递给setDetectChangeHandler并在set. 这提供了更多的控制。
2021-04-20 18:22:06
你能解释一下你的解决方案是如何工作的吗?它在做什么?
2021-04-28 18:22:06
我认为这是最好的开箱即用的解决方案。在我的应用程序中完美运行,我喜欢它,因为它不影响原始设置器。
2021-05-01 18:22:06
对于表单调用中的每个字段: setDetectChangeHandler(document.querySelector('.myFrom .fieldN')); 然后为捕获所有更改添加一个监听器到表单 document.querySelector('.myForm').addEventListener('change', event => { // 在此处处理字段更改 });
2021-05-04 18:22:06
我如何使用它?field是什么?
2021-05-15 18:22:06