48

I would like to use a MutationObserver object to observe changes to some of my DOM nodes.

The docs give an example of creating a MutationObserver object and registering it on a target.

// select the target node
var target = document.querySelector('#some-id');

// create an observer instance
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    console.log(mutation.type);
  });    
});

// 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);

Say I have the code above, but just under it, I place this code:

var target2 = document.querySelector('#some-other-id');
var config2 = {attributes: true, subtree: true};
observer.observe(target2, config2);

Will observer:

  • now be observing 2 targets?
  • will it stop observing target?
  • will it decide not to observe target2?
  • will it throw an error?
  • or will it exhibit some other behavior?
Luke
  • 5,567
  • 4
  • 37
  • 66

2 Answers2

47

The observer will now be watching two targets - target and target2 per your definitions. No error will be thrown, and target will not be "unregistered" in favor of target2. No unexpected or other behaviors will be exhibited.

Here is a sample which uses the same MutationObserver on two contenteditable elements. To view this, delete the <span> node from each contenteditable element and view the behavior span across both observed elements.

<div id="myTextArea" contenteditable="true">
    <span contenteditable="false">Span A</span>
</div>

<div id="myTextArea2" contenteditable="true">
    <span contenteditable="false">Span B</span>
</div>

var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
      //console.log($(mutation.removedNodes)); // <<-- includes text nodes

      $(mutation.removedNodes).each(function(value, index) {
          if(this.nodeType === 1) {
              console.log(this)
          }
      });
  });
});

var config = { attributes: true, childList: true, characterData: true };

observer.observe($('#myTextArea')[0], config);

observer.observe($('#myTextArea2')[0], config);

JSFiddle Link - demo

Note that I have recycled the same config for this first demo, but, placing a new config will be exclusive to that observed element. Taking your example as defined in config2, if used on #myTextArea2, you'll not see the logged node per the configuration options, but notice that the observer for #myTextArea is unaffected.

JSFiddle Link - demo - configuration exclusiveness

scniro
  • 16,844
  • 8
  • 62
  • 106
  • 1
    Very nice! Though I wonder if this works consistently in all browsers, and I wonder if it's consistent if you happen to observe an element within an element you started observing? – NoBugs Jun 13 '15 at 05:48
  • 6
    Something to also note is that observing the same target more than once, will not result in multiple calls to the callback for changes to that element. – major-mann Mar 13 '16 at 10:11
  • @NoBugs @major-mann Neither multiple observations on the same element nor observations on 2 elements--where one was within the other--would result in multiple mutation records (nor in multiple `mutation.addedNodes`) being passed the the listening function when I tested on Chrome 56. – jdunk Apr 01 '17 at 09:09
  • 10
    There's a problem with this: how do we *unobserve* one of the element we're observing? It seems like the `disconnect()` method will make a MutationObserver stop observing all the elements! This means there's no cleanup per element, which means possibility of memory leaks!! – trusktr Jul 26 '17 at 23:59
  • 1
    Well I guess you have to disconnect and then re-observe just the elements you need. I'd almost prefer to pass it a live HTMLCollection and have it watch all of those elements automatically even when nodes are added and removed. – James Coyle Jan 22 '18 at 11:56
  • is it possible to observe an `{}` for changes with `MutationObserver`?? – oldboy Jan 24 '20 at 22:42
  • For the above memory leak concern, the disconnect doc on MDN have this doc: "If the element being observed is removed from the DOM, and then subsequently released by the browser's garbage collection mechanism, the MutationObserver is likewise deleted." – Apolo Jan 12 '21 at 17:25
  • @trusktr did you figure out how to unobserve just one? – theonlygusti Sep 09 '21 at 11:18
  • @Apolo That only works if the MutationObserver is not also observing any other nodes that are still connected. In that case, the MutationObserver will not be deleted (hence leak). – trusktr Sep 09 '21 at 23:59
  • 1
    @theonlygusti Seems there are only two options: use one MO per element, or call disconnect on a single MO then observe again on all the elements except for the one you no longer wish to observe. There's also a spec discussion on adding an API for it: https://github.com/WICG/webcomponents/issues/940 – trusktr Sep 10 '21 at 00:01
  • @trusktr I don't think so, I believe MO uses weak references to allow GC – Apolo Sep 10 '21 at 12:19
  • @Apolo Is that in the spec? So if the MO watches a set of elements, and you remove one element from DOM and no longer reference it, that element is collected even though the MO will not be collected (because it has to keep watching the other nodes)? – trusktr Sep 11 '21 at 02:03
  • @Apolo As far as I can tell from the spec, there's no mention of MutationObserver having a WeakMap (https://dom.spec.whatwg.org/#interface-mutationobserver). Theoretically MO could be implemented so that it uses WeakRef and FinalizationRegistry for all nodes, and could then automatically unobserve nodes when they are no longer referenced, even if some nodes are still being observed and the MO still needs to be kept alive. But it's not a guarantee that we have. Seems that the spec needs an update in some form regardless, to make unobservation of a subset of nodes officially possible. – trusktr Sep 11 '21 at 02:17
  • @theonlygusti Oops, I pasted the wrong link in my last comment to you, so reposting: Seems there are only two options: use one MO per element, or call disconnect on a single MO then observe again on all the elements except for the one you no longer wish to observe. There's also a spec discussion on adding an API for it: https://github.com/whatwg/dom/issues/126 – trusktr Sep 11 '21 at 02:20
  • 1
    @trusktr I think it's here: "Mutation observers > Garbage Collection" https://dom.spec.whatwg.org/#garbage-collection "Registered observers in a node’s registered observer list have a weak reference to the node." – Apolo Sep 13 '21 at 09:42
  • @Apolo Indeed, and after some testing, it seems that letting go of elements collects them (and I assume MO will no longer observe them) which is good for cases when we want to unobserve elements and will no longer need them later. – trusktr Sep 15 '21 at 05:40
0

(This should be a comment to the accepted answer but I don't have enough reputation)

When you have a MutationObserver m that is currently observing multiple nodes and you want to "disconnect" from only one node e, you can do the following:

m.observe(e, { attribute: true, attributeFilter: [] });

This removes the preexisting observers on e (MDN Docs link) and adds a dummy observer on e.

Scorbie
  • 21
  • 1
  • 2
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/34820351) – Harrison Aug 15 '23 at 12:06