Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MutationObserver behavior after the element was removed

I have a MutationObserver binded to a #foo html element.

var target = document.querySelector("#foo");
var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        // ...
    });
});

When user clicks #button element, the #foo is removed and appears again after a second.

<div id="button">Click me to remove the Foo!</div>
<div id="foo">Hello, I'm Foo!</div>

And as I noticed, after the #foo was removed and recreated, the observer stopped working.

  • Is that the normal behavior?
  • So, I need to launch the observer again?

And the third quesion:

  • Is the first observer is totally removed? Or, instead of that, it is still running, with just "doing nothing"? - I'm asking about it, because I don't want the multiple observers run, if only one of them doing the real work.
like image 678
john c. j. Avatar asked Feb 07 '16 12:02

john c. j.


People also ask

How do you stop MutationObserver?

disconnect() The MutationObserver method disconnect() tells the observer to stop watching for mutations. The observer can be reused by calling its observe() method again.

Is MutationObserver slow?

Good answer! Yes, it's finally slower by just 10% or even less in recent versions of Chrome. @Bergi, it's for the case when processing is complex and takes a lot of time. MutationObserver runs in a microtask queue so multiple callbacks may still reside in one task, which can lead to very long paint frames and jank.

What is window MutationObserver?

The MutationObserver interface provides the ability to watch for changes being made to the DOM tree. It is designed as a replacement for the older Mutation Events feature, which was part of the DOM3 Events specification.

How does a mutation observer work?

MutationObserver is a Web API provided by modern browsers for detecting changes in the DOM. With this API one can listen to newly added or removed nodes, attribute changes or changes in the text content of text nodes.


1 Answers

and appears again after a second

The behavior will depend entirely on what you mean by that.

If you mean a new element with the same id is created, then yes, this is expected behavior: When you call observer.observe on an element, you're calling it on that specific element, not any element matching its ID. If you remove that element from the DOM and replace it with another, different element that looks the same, the observer is not magically hooked up to that new, different element:

var target = document.querySelector("#foo");
var observer = new MutationObserver(function(mutations) {
    console.log("#foo was modified");
});
observer.observe(target, {subTree: true, childList: true});
var counter = 0;
tick();
setTimeout(function() {
    console.log("Replacing foo with a new one");
    target.parentNode.removeChild(target);
    target = document.createElement("div");
    target.id = "foo";
    target.innerHTML = "This is the new foo";
    document.body.insertBefore(target, document.body.firstChild);
}, 1000);
function tick() {
    target.appendChild(document.createTextNode("."));
    if (++counter < 20) {
        setTimeout(tick, 200);
    }
}
<div id="foo">This is the original foo</div>

If you mean you're putting the same element back in the DOM, then no, that wouldn't be expected behavior, and not what happens in this example:

var target = document.querySelector("#foo");
var observer = new MutationObserver(function(mutations) {
    console.log("#foo was modified");
});
observer.observe(target, {subTree: true, childList: true});
var counter = 0;
tick();
setTimeout(function() {
    console.log("Removing foo for a moment");
    target.parentNode.removeChild(target);
    setTimeout(function() {
        console.log("Putting the same foo back in");
        document.body.insertBefore(target, document.body.firstChild);
    }, 100);
}, 1000);
function tick() {
    target.appendChild(document.createTextNode("."));
    if (++counter < 20) {
        setTimeout(tick, 200);
    }
}
<div id="foo">This is the original foo</div>

The key take-away message here is that the observer is observing the one specific element you pass into observe.


These questions only apply if you're removing the old element and replacing it with a whole new one:

So, I need to launch the observer again?

If you're removing the original element and putting a new one in its place, you should tell the observer to stop observing:

observer.disconnect();

Once you have a new element, you can connect the observer to it:

observer.observe(theNewElement, /*...options go here...*/);

Is the first observer is totally removed? Or, instead of that, it is still running, with just "doing nothing"?

In theory, since the spec makes it clear that it's the element that has a reference to its observers, then if you release your only reference to the element, in theory the observer gets disconnected because the element and its list of observers gets cleaned up. However, it's unclear how the observer's disconnect method can be implemented without the observer having a reference back to the element, so they may have mutual references. If you keep your reference to the observer, that would keep the mutual references in memory. I'd disconnect it on purpose, to be sure.

I'm asking about it, because I don't want the multiple observers run, if only one of them doing the real work.

Observers don't "run," they react. The observer will continue observing the original element until/unless you disconnect it. If you're not doing anything to the original element, the observer isn't doing anything other than taking up memory. But again, if you're going to remove the element, you should disconnect the observer.

like image 156
T.J. Crowder Avatar answered Sep 22 '22 16:09

T.J. Crowder