Are the MutationRecords given by MutationObserver guaranteed to be in order?

158 views Asked by At

I'm curious whether or not the batch of mutations that are received by a MutationObserver is returned in order or not.

var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    console.log(mutation.type); // are these in order?
  });    
});

Thanks!

1

There are 1 answers

0
trusktr On

No. They are not guaranteed to be in order, which makes things really difficult.

Take a look at this example, for example:

https://codepen.io/trusktr/pen/OJveVbv/d7f4cc48f8fa3b7462ae8043157ba05d

const connected = new Set 
const disconnected = new Set 
let scheduled = false

class XEl extends HTMLElement {
    connectedCallback() {
        this.o = new MutationObserver(changes => {
            for (const change of changes) {
                console.log('--- change for target', change.target.id)
                for (const child of change.addedNodes) {
                    console.log('track added child', child)
                    connected.add(child)
                }
                for (const child of change.removedNodes) {
                    console.log('track removed child', child)
                    disconnected.add(child)
                }
            }

            if (!scheduled) {
                scheduled = true
                queueMicrotask(() => {
                    console.log('--------------- MICROTASK MO')
                    scheduled = false
                    const allNodes = new Set([...connected, ...disconnected])
                    for (const child of allNodes) {
                        if (child.parentElement) {
                            if (disconnected.has(child)) console.log('child removed:', child)
                            if (connected.has(child)) console.log('child added:', child)
                        } else {
                            if (connected.has(child)) console.log('child added:', child)
                            if (disconnected.has(child)) console.log('child removed:', child)
                        }
                    }
                    connected.clear()
                    disconnected.clear()
                })
            }
        })

        this.o.observe(this, {childList: true})
    }

    disconnectedCallback() {
        this.o.disconnect()
    }
}

customElements.define('x-el', XEl)

setTimeout(() => {
    queueMicrotask(() => console.log('--------------- MICROTASK before'))
    const t = three
    
    t.remove() // queue mutation for two
    one.append(t) // queue mutation for one
    
    t.remove() // queue mutation for one
    two.append(t) // queue mutation for two
    
    t.remove() // queue mutation for two
    one.append(t) // queue mutation for one
    
    t.remove() // queue mutation for one
    two.append(t) // queue mutation for two
    
    queueMicrotask(() => console.log('--------------- MICROTASK after'))
}, 1000)
<x-el id="one">
    one
    <x-el id="two">
        two
        <x-el id="three">
            three
        </x-el>
    </x-el>
</x-el>

Notice in the console output that all records for one parent element are looped over first, then all records for the other parent are looped over.

This is entirely different than the code that actually ran, in which it alternated between parent one and parent two.


On a side note, Mutation Observer is very difficult to use compared to DOM Mutation Events. I strongly believe that a better intuitive non-performance-killing version of an API similar to Mutation Events could have been created instead of MutationObserver's childList API.

More details on the complexity of MutationObserver vs the simplicity of Mutation Events regarding the developer experience here:

https://github.com/whatwg/dom/issues/1105

Please express the need for a simpler API there if you agree!