Javascript 检测 DOM 中的变化

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3219758/
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-08-23 03:43:47  来源:igfitidea点击:

Detect changes in the DOM

javascriptdommutation-eventsmutation-observers

提问by esafwan

I want to execute a function when some div or input are added to the html. Is this possible?

我想在将某些 div 或输入添加到 html 时执行一个函数。这可能吗?

For example, a text input is added, then the function should be called.

例如,添加了文本输入,则应调用该函数。

采纳答案by gblazex

2015 update, new MutationObserveris supported by modern browsers:

2015 更新,MutationObserver现代浏览器支持新的:

Chrome 18+, Firefox 14+, IE 11+, Safari 6+

Chrome 18+、Firefox 14+、IE 11+、Safari 6+

If you need to support older ones, you may try to fall back to other approaches like the ones mentioned in this 5(!) year old answer below. There be dragons. Enjoy :)

如果您需要支持较旧的方法,您可以尝试使用其他方法,例如下面这个5(!) 岁的答案中提到的方法。有龙。享受 :)



Someone else is changing the document? Because if you have full control over the changes you just need to create your own domChangedAPI - with a function or custom event - and trigger/call it everywhere you modify things.

其他人正在更改文档?因为如果您可以完全控制更改,您只需要创建自己的domChangedAPI - 使用函数或自定义事件 - 并在您修改内容的任何地方触发/调用它。

The DOM Level-2 has Mutation event types, but older version of IE don't support it. Note that the mutation events are deprecated in the DOM3 Events specand have a performance penalty.

DOM Level-2的具有突变的事件类型,但IE浏览器的旧版本不支持它。请注意,更改事件在 DOM3 事件规范中已被弃用,并且会降低性能

You can try to emulate mutation event with onpropertychangein IE(and fall back to the brute-force approach if non of them is available).

您可以尝试onpropertychange在 IE 中模拟突变事件(如果没有可用的方法,则退回到蛮力方法)。

For a fulldomChange an interval could be an over-kill. Imagine that you need to store the current state of the whole document, and examine every element's every property to be the same.

对于完整的domChange,间隔可能会过大。想象一下,您需要存储整个文档的当前状态,并检查每个元素的每个属性是否相同。

Maybe if you're only interested in the elements and their order (as you mentioned in your question), a getElementsByTagName("*")can work. This will fire automatically if you add an element, remove an element, replace elements or change the structure of the document.

也许如果您只对元素及其顺序感兴趣(正如您在问题中提到的),agetElementsByTagName("*")可以工作。如果您添加元素、删除元素、替换元素或更改文档结构,这将自动触发。

I wrote a proof of concept:

我写了一个概念证明:

(function (window) {
    var last = +new Date();
    var delay = 100; // default delay

    // Manage event queue
    var stack = [];

    function callback() {
        var now = +new Date();
        if (now - last > delay) {
            for (var i = 0; i < stack.length; i++) {
                stack[i]();
            }
            last = now;
        }
    }

    // Public interface
    var onDomChange = function (fn, newdelay) {
        if (newdelay) delay = newdelay;
        stack.push(fn);
    };

    // Naive approach for compatibility
    function naive() {

        var last = document.getElementsByTagName('*');
        var lastlen = last.length;
        var timer = setTimeout(function check() {

            // get current state of the document
            var current = document.getElementsByTagName('*');
            var len = current.length;

            // if the length is different
            // it's fairly obvious
            if (len != lastlen) {
                // just make sure the loop finishes early
                last = [];
            }

            // go check every element in order
            for (var i = 0; i < len; i++) {
                if (current[i] !== last[i]) {
                    callback();
                    last = current;
                    lastlen = len;
                    break;
                }
            }

            // over, and over, and over again
            setTimeout(check, delay);

        }, delay);
    }

    //
    //  Check for mutation events support
    //

    var support = {};

    var el = document.documentElement;
    var remain = 3;

    // callback for the tests
    function decide() {
        if (support.DOMNodeInserted) {
            window.addEventListener("DOMContentLoaded", function () {
                if (support.DOMSubtreeModified) { // for FF 3+, Chrome
                    el.addEventListener('DOMSubtreeModified', callback, false);
                } else { // for FF 2, Safari, Opera 9.6+
                    el.addEventListener('DOMNodeInserted', callback, false);
                    el.addEventListener('DOMNodeRemoved', callback, false);
                }
            }, false);
        } else if (document.onpropertychange) { // for IE 5.5+
            document.onpropertychange = callback;
        } else { // fallback
            naive();
        }
    }

    // checks a particular event
    function test(event) {
        el.addEventListener(event, function fn() {
            support[event] = true;
            el.removeEventListener(event, fn, false);
            if (--remain === 0) decide();
        }, false);
    }

    // attach test events
    if (window.addEventListener) {
        test('DOMSubtreeModified');
        test('DOMNodeInserted');
        test('DOMNodeRemoved');
    } else {
        decide();
    }

    // do the dummy test
    var dummy = document.createElement("div");
    el.appendChild(dummy);
    el.removeChild(dummy);

    // expose
    window.onDomChange = onDomChange;
})(window);

Usage:

用法:

onDomChange(function(){ 
    alert("The Times They Are a-Changin'");
});

This works on IE 5.5+, FF 2+, Chrome, Safari 3+ and Opera 9.6+

这适用于 IE 5.5+、FF 2+、Chrome、Safari 3+ 和 Opera 9.6+

回答by vsync

This is the ultimate approach so far, with smallest code:

这是迄今为止的终极方法,代码最少:

IE9+, FF, Webkit:

IE9+、FF、Webkit:

Using MutationObserverand falling back to the deprecated Mutation eventsif needed:
(Example below if only for DOM changes concerning nodes appended or removed)

如果需要,使用MutationObserver并回退到已弃用的 Mutation 事件:(
下面的示例仅适用于与附加或删除的节点相关的 DOM 更改)

var observeDOM = (function(){
  var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

  return function( obj, callback ){
    if( !obj || !obj.nodeType === 1 ) return; // validation

    if( MutationObserver ){
      // define a new observer
      var obs = new MutationObserver(function(mutations, observer){
          callback(mutations);
      })
      // have the observer observe foo for changes in children
      obs.observe( obj, { childList:true, subtree:true });
    }
    
    else if( window.addEventListener ){
      obj.addEventListener('DOMNodeInserted', callback, false);
      obj.addEventListener('DOMNodeRemoved', callback, false);
    }
  }
})();

//------------< DEMO BELOW >----------------
// add item
var itemHTML = "<li><button>list item (click to delete)</button></li>",
    listElm = document.querySelector('ol');

document.querySelector('body > button').onclick = function(e){
  listElm.insertAdjacentHTML("beforeend", itemHTML);
}

// delete item
listElm.onclick = function(e){
  if( e.target.nodeName == "BUTTON" )
    e.target.parentNode.parentNode.removeChild(e.target.parentNode);
}
    
// Observe a specific DOM element:
observeDOM( listElm, function(m){ 
   var addedNodes = [], removedNodes = [];

   m.forEach(record => record.addedNodes.length & addedNodes.push(...record.addedNodes))
   
   m.forEach(record => record.removedNodes.length & removedNodes.push(...record.removedNodes))

  console.clear();
  console.log('Added:', addedNodes, 'Removed:', removedNodes);
});


// Insert 3 DOM nodes at once after 3 seconds
setTimeout(function(){
   listElm.removeChild(listElm.lastElementChild);
   listElm.insertAdjacentHTML("beforeend", Array(4).join(itemHTML));
}, 3000);
<button>Add Item</button>
<ol>
  <li><button>list item (click to delete)</button></li>
  <li><button>list item (click to delete)</button></li>
  <li><button>list item (click to delete)</button></li>
  <li><button>list item (click to delete)</button></li>
  <li><em>&hellip;More will be added after 3 seconds&hellip;</em></li>
</ol>

回答by pie6k

I have recently written a plugin that does exactly that - jquery.initialize

我最近写了一个插件就是这样做的 - jquery.initialize

You use it the same way as .eachfunction

您可以像使用.each函数一样使用它

$(".some-element").initialize( function(){
    $(this).css("color", "blue"); 
});

The difference from .eachis - it takes your selector, in this case .some-elementand wait for new elements with this selector in the future, if such element will be added, it will be initialized too.

与此不同的.each是 - 它需要您的选择器,在这种情况下,.some-element并在将来等待具有此选择器的新元素,如果将添加此类元素,它也将被初始化。

In our case initialize function just change element color to blue. So if we'll add new element (no matter if with ajax or even F12 inspector or anything) like:

在我们的例子中,初始化函数只是将元素颜色更改为蓝色。因此,如果我们要添加新元素(无论是使用 ajax 还是 F12 检查器或其他任何东西),例如:

$("<div/>").addClass('some-element').appendTo("body"); //new element will have blue color!

Plugin will init it instantly. Also plugin makes sure one element is initialized only once. So if you add element, then .detach()it from body and then add it again, it will not be initialized again.

插件会立即初始化它。插件还确保一个元素只初始化一次。因此,如果您添加元素,然后将.detach()其从主体中添加,然后再次添加,则不会再次初始化。

$("<div/>").addClass('some-element').appendTo("body").detach()
    .appendTo(".some-container");
//initialized only once

Plugin is based on MutationObserver- it will work on IE9 and 10 with dependencies as detailed on the readme page.

插件基于MutationObserver- 它将在 IE9 和 10 上工作,依赖项详见自述页面

回答by HB MAAM

or you can simply Create your own event, that run everywhere

或者你可以简单地创建你自己的事件,到处运行

 $("body").on("domChanged", function () {
                //dom is changed 
            });


 $(".button").click(function () {

          //do some change
          $("button").append("<span>i am the new change</span>");

          //fire event
          $("body").trigger("domChanged");

        });

Full example http://jsfiddle.net/hbmaam/Mq7NX/

完整示例 http://jsfiddle.net/hbmaam/Mq7NX/

回答by Anthony Awuley

This is an example using MutationObserverfrom Mozillaadapted from this blog post

这是一个使用Mozilla 的MutationObserver的例子,改编自这篇博文

Chrome 18+, Firefox 14+, IE 11+, Safari 6+

Chrome 18+、Firefox 14+、IE 11+、Safari 6+

// Select the node that will be observed for mutations
var targetNode = document.getElementById('some-id');

// Options for the observer (which mutations to observe)
var config = { attributes: true, childList: true };

// Callback function to execute when mutations are observed
var callback = function(mutationsList) {
    for(var mutation of mutationsList) {
        if (mutation.type == 'childList') {
            console.log('A child node has been added or removed.');
        }
        else if (mutation.type == 'attributes') {
            console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }
};

// Create an observer instance linked to the callback function
var observer = new MutationObserver(callback);

// Start observing the target node for configured mutations
observer.observe(targetNode, config);

// Later, you can stop observing
observer.disconnect();

回答by Anthony Awuley

Use the MutationObserverinterface as shown in Gabriele Romanato's blog

使用MutationObserver界面,如 Gabriele Romanato 的博客中所示

Chrome 18+, Firefox 14+, IE 11+, Safari 6+

Chrome 18+、Firefox 14+、IE 11+、Safari 6+

// The node to be monitored
var target = $( "#content" )[0];

// Create an observer instance
var observer = new MutationObserver(function( mutations ) {
  mutations.forEach(function( mutation ) {
    var newNodes = mutation.addedNodes; // DOM NodeList
    if( newNodes !== null ) { // If there are new nodes added
        var $nodes = $( newNodes ); // jQuery set
        $nodes.each(function() {
            var $node = $( this );
            if( $node.hasClass( "message" ) ) {
                // do something
            }
        });
    }
  });    
});

// Configuration of the observer:
var config = { 
    attributes: true, 
    childList: true, 
    characterData: true 
};

// Pass in the target node, as well as the observer options
observer.observe(target, config);

// Later, you can stop observing
observer.disconnect();

回答by StartCoding

How about extending a jquery for this?

为此扩展一个jquery怎么样?

   (function () {
        var ev = new $.Event('remove'),
            orig = $.fn.remove;
        var evap = new $.Event('append'),
           origap = $.fn.append;
        $.fn.remove = function () {
            $(this).trigger(ev);
            return orig.apply(this, arguments);
        }
        $.fn.append = function () {
            $(this).trigger(evap);
            return origap.apply(this, arguments);
        }
    })();
    $(document).on('append', function (e) { /*write your logic here*/ });
    $(document).on('remove', function (e) { /*write your logic here*/ ) });

Jquery 1.9+ has built support for this(I have heard not tested).

Jquery 1.9+ 已经建立了对此的支持(我听说没有测试过)。