Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does MutationObserver fire twice for childList but never for characterData?

I have a simple MutationObserver setup as a test. The HTML has a span whose text content gets updated once per second (and a div for messages):

<span class="tester"></span>
<div id="msg"></div>

The MutationObserver is set to watch .tester and writes text to the #msg div when it observes a change. Meanwhile, a setInterval() runs once/second to change the text in .tester:

var target = document.querySelector('.tester');

var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    console.log(mutation.type);
    $('#msg').append(mutation.type+"<br/>")
    setTimeout(function() { $('#msg').text(''); }, 500);
  });    
});

var config = { attributes: true, childList: true, characterData: true };
observer.observe(target, config);

setInterval(function() {
  $('#msg').text('');
  $('.tester').text("update: "+Math.random())
}, 1000);

I would expect this code to print once per second that the characterData has changed. According to Mozilla's docs for MutationObserver, it says about characterData: "Set to true if mutations to target's data are to be observed." Instead, I see no characterData mutations but do see two childList mutations every second.

Why am I not seeing any characterData mutations, and why am I seeing two childList mutations?

Here's a working example with CodePen.

like image 248
mix Avatar asked Feb 25 '16 23:02

mix


People also ask

How MutationObserver works?

Overview. 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.

What is childList?

childList – changes in the direct children of node , subtree – in all descendants of node , attributes – attributes of node , attributeFilter – an array of attribute names, to observe only selected ones. characterData – whether to observe node.

What is a 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 to use Mutation observer in js?

This is done by setting the characterData property to true in the options object. Here's the code: let options = { characterData: true }, observer = new MutationObserver(mCallback); function mCallback(mutations) { for (let mutation of mutations) { if (mutation. type === 'characterData') { // Do something here... } } }


1 Answers

The reason is as Jeremy Banks said: When you use jQuery's text(), it removes all the text nodes and then adds in new ones. That's not a change to character data, it's a change to the childList: Removing the node that's there and replacing it with a new one.

To see a change to character data, you have to modify the existing text node's nodeValue, and observe subtree modifications:

var target = document.querySelector('.tester');

var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        console.log(mutation.type);
        $('#msg').append(mutation.type+"<br/>")
        setTimeout(function() { $('#msg').text(''); }, 500);
    });    
});

var config = {
    attributes: true,
    childList: true,
    characterData: true,
    subtree: true                   // <=== Change, added subtree
};
observer.observe(target, config);

var timer = setInterval(function() {
    $('#msg').text('');
    // Change --VVVVV modifying the existing child node
    $('.tester')[0].firstChild.nodeValue = "updated" + Math.random();
}, 1000);

// Stop after 10 seconds
setTimeout(function() {
    clearInterval(timer);
}, 10000);
<span class="tester">x</span><!-- Change, added a starting child node -->
<div id="msg"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

Re your question about why there are two childList mutations, yes I think you're right: They're removing the child, then adding a new one. If we use the replaceChild method, we see only a single mutation:

var target = document.querySelector('.tester');

var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        console.log(mutation.type);
        $('#msg').append(mutation.type+"<br/>")
        setTimeout(function() { $('#msg').text(''); }, 500);
    });    
});

var config = {
    attributes: true,
    childList: true,
    characterData: true,
    subtree: true                   // <=== Change, added subtree
};
observer.observe(target, config);

var timer = setInterval(function() {
    $('#msg').text('');
    // Change --VVVVV modifying the existing child node
    var text = document.createTextNode("updated" + Math.random());
    var parent = $('.tester')[0];
    parent.replaceChild(text, parent.firstChild);
}, 1000);

// Stop after 10 seconds
setTimeout(function() {
    clearInterval(timer);
}, 10000);
<span class="tester">x</span><!-- Change, added a starting child node -->
<div id="msg"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
like image 107
T.J. Crowder Avatar answered Oct 19 '22 16:10

T.J. Crowder