使用 MutationObserver 检测输入值变化

Posted

技术标签:

【中文标题】使用 MutationObserver 检测输入值变化【英文标题】:Detect input value change with MutationObserver 【发布时间】:2015-11-29 17:51:28 【问题描述】:

我想检测输入字段中的文本/值何时发生变化。即使我用js改变了值,我也想检测到变化。

这是我迄今为止在demo in 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);

【问题讨论】:

如果你不耐烦,现在想要一个可怕的、糟糕的、糟糕的修复,那么我为你做了这个:IDL-Property-Observe。运行此库后,您的上述代码将以牺牲原生原型的最佳实践为代价运行良好。干杯! 【参考方案1】:

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

Properties and Attributes in html .prop() vs .attr() What is happening behind .setAttribute vs .attribute=?

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

targetNode.value="foo";

浏览器更新value 属性,但不更新value 属性(它反映了defaultValue 属性)。

然后,如果我们查看spec of MutationObserver,我们会发现attributes 是可以使用的对象成员之一。所以如果你显式设置value属性:

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

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

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

查看此SO question 了解不同的替代方案及其限制。

【讨论】:

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

我对@9​​87654321@ 做了一点修改,想分享一下。不敢相信实际上有解决方案。

在输入框中键入以查看默认行为。现在,打开 DevTools 并选择输入元素,然后更改其值,例如$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;
            
        );
    
&lt;input type="text" id="inputBox" placeholder="Enter something" /&gt;

【讨论】:

【参考方案3】:

价值属性可以观察到,别浪费时间了。

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>

【讨论】:

我正在研究一个类似的问题,我对这个答案很感兴趣,但我有点挣扎。这仅适用于 textarea 的权利?由于您设置了出现在 textarea 字段内的 textContent,但 textContent 不会出现在 HTML 的输入字段内。 @Josh 输入字段使用return this.defaultValue = t; :) 您能否详细说明您的代码是如何工作的?【参考方案4】:

这有效并保留并链接了原始的 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);
  

【讨论】:

您能解释一下您的解决方案是如何工作的吗?它在做什么? 我认为这是开箱即用的最佳解决方案。在我的应用程序中完美运行,我喜欢它,因为它让原始设置器不受影响。 对于表单调用中的每个字段:setDetectChangeHandler(document.querySelector('.myFrom .fieldN'));然后为了捕捉所有变化,在表单中添加一个监听器 document.querySelector('.myForm').addEventListener('change', event => // 在这里处理字段变化 ); 我喜欢这个解决方案。尽管这在特定情况下会导致一个小问题。我已向具有去抖动行为的字段添加了一个事件侦听器(用于输入事件)。我在字段中输入的值会在 500 毫秒后更新(更正货币格式)。所以我更新了字段,字段更新了自己(它的值)。如果我使用这个 (setDetectChangeHandler) 方法,我最终会出现一个循环。 "input" 事件连续触发。因此,我没有在set 中触发事件,而是将回调传递给setDetectChangeHandler 并在set 中调用它。这提供了更多控制权。 我该如何使用它? field 是什么?

以上是关于使用 MutationObserver 检测输入值变化的主要内容,如果未能解决你的问题,请参考以下文章

MutationObserver 在整个 DOM 中检测节点的性能

javascript 检测dom变化#MutationObserver #js

javascript 检测dom变化#MutationObserver #js

MutationObserver 类更改

Mutation Observer 无法检测到元素的 dom 移除

MutationObserver 点击元素/类