</> HTML5Advent
ENFRESDEITPT

// apis

¿Qué es MutationObserver? El tercer observador, y la cola que lo hace usable

IntersectionObserver vigila la visibilidad, ResizeObserver el tamaño, MutationObserver el propio DOM. Qué informa, por qué sustituyó a los Mutation Events, y el método que revela cómo funciona realmente la entrega.

Un cuaderno de espiral abierto con hojas cuadriculadas en blanco y un bolígrafo negro encima, junto a un pasaporte y un teléfono sobre una mesa de madera

La plataforma tiene tres observadores y se reparten el trabajo con claridad. IntersectionObserver tells you when an element enters the viewport. ResizeObserver tells you when it changes size. MutationObserver tells you when the DOM itself changes.

MDN lo resume en una línea: la interfaz «ofrece la capacidad de vigilar los cambios que se hacen en el árbol 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".

Primer plano de un bloc de notas de papel sobre un escritorio, con un bolígrafo y unas gafas al 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.

Un bloc de notas esperando ser leído. takeRecords() hace exactamente eso: vacía la cola pendiente del observador y te entrega lo acumulado desde la última entrega.