</> HTML5Advent
ENFRESDEITPT

// apis

O que é o MutationObserver? O terceiro observador, e a fila que o torna utilizável

O IntersectionObserver vigia a visibilidade, o ResizeObserver o tamanho, o MutationObserver o próprio DOM. O que reporta, porque substituiu os Mutation Events, e o método que revela como funciona realmente a entrega.

Um caderno de espiral aberto em folhas quadriculadas em branco com uma caneta preta pousada por cima, ao lado de um passaporte e de um smartphone numa mesa de madeira

A plataforma tem três observadores, e dividem o trabalho com clareza. IntersectionObserver tells you when an element enters the viewport. ResizeObserver tells you when it changes size. MutationObserver tells you when the DOM itself changes.

A MDN resume numa linha: a interface «oferece a capacidade de observar as alterações feitas à árvore DOM».

What it can watch

Three kinds of change, selected through the options you pass:

  • childList — nodes added or removed
  • attributes — an attribute changed on the observed element
  • characterData — the text content of a node changed

Plus subtree, which extends any of the above to every descendant rather than the target alone. That one word is the difference between watching a container and watching everything inside it.

const observer = new MutationObserver((records) =&gt; {
  for (const record of records) {
    if (record.type === 'childList') {
      console.log(record.addedNodes.length, 'node(s) added');
    }
  }
});

observer.observe(targetNode, {
  attributes: true,
  childList: true,
  subtree: true,
});

Why it replaced Mutation Events

MDN is explicit about the lineage: MutationObserver "is designed as a replacement for the older Mutation Events feature, which was part of the DOM3 Events specification".

Mutation Events fired synchronously, one event per change, in the middle of whatever was modifying the DOM. A script inserting a hundred nodes produced a hundred interruptions, and a handler that touched the DOM could trigger more events while still handling the first. They were removed for good reason.

The queue is the design

The interesting part is not the callback but what feeds it, and one method gives it away. takeRecords() "removes all pending notifications from the MutationObserver's notification queue and returns them in a new Array of MutationRecord objects".

Grande plano de um bloco de notas de papel sobre uma secretária, com uma caneta e uns óculos ao lado

A pending notification queue means delivery is not immediate. Changes accumulate, and your callback receives an array of records rather than one call per change. That is precisely what makes the API usable where Mutation Events were not: a hundred insertions become one callback with a hundred records, and your code cannot interrupt the operation that caused them.

It also explains when takeRecords() earns its place. Before calling disconnect(), anything already queued but not yet delivered would be lost - draining the queue first lets you handle it.

Two things that catch people out

Your own writes are observed too. If the callback modifies the DOM inside the observed subtree, those modifications queue up and call you again. The usual remedy is a guard flag, or narrowing the options so your own writes fall outside what you watch.

Attribute records do not carry the new value by default. You get the attribute name, and attributeOldValue: true in the options if you want the previous one. The current value you read from the element yourself.

When to reach for it

MutationObserver is the right tool when the DOM is changed by something you do not control: a third-party widget, a CMS-injected block, an editor. It is the wrong tool for your own application state, where you already know when things change and observing them is a slower way of asking yourself a question you can answer directly.

And disconnect() matters more than it looks - MDN describes it as stopping notifications "until and unless observe() is called again". An observer on a subtree that outlives the component watching it is a leak that no one notices until the page has been open for an hour.

Um bloco à espera de ser lido. É exatamente isso que o takeRecords() faz: esvazia a fila pendente do observador e entrega-lhe o que se acumulou desde a última entrega.