2 de agosto de 2026

Mutation observer

MutationObserver es un objeto nativo que observa un elemento DOM y dispara un callback cuando hay cambios en él.

Primero veremos su sintaxis, luego exploraremos un caso de la vida real para ver dónde puede ser útil.

Sintaxis

MutationObserver es fácil de usar.

Primero creamos un observador con una función callback:

let observer = new MutationObserver(callback);

Y luego lo vinculamos a un nodo DOM:

observer.observe(node, config);

config es un objeto con opciones booleanas “a qué clase de cambios reaccionar”:

  • childList – cambios en los hijos directos de node,
  • subtree – en todos los descendientes de node,
  • attributes – atributos de node,
  • attributeFilter – un array de nombres de atributos, para observar solamente a los seleccionados,
  • characterData – establece si debe observar cambios de texto en node.data o no,

Algunas otras opciones:

  • attributeOldValue – si es true, tanto el valor viejo como el nuevo del atributo son pasados al callback (ver abajo), de otro modo pasa solamente el nuevo (necesita la opción attributes),
  • characterDataOldValue – si es true, tanto el valor viejo como el nuevo de node.data son pasados al callback (ver abajo), de otro modo pasa solamente el nuevo (necesita la opción characterData).

Entonces, después de cualquier cambio, el callback es ejecutado: los cambios son pasados en el primer argumento como una lista objetos MutationRecord, y el observador en sí mismo como segundo argumento.

Los objetos MutationRecord tienen como propiedades:

  • type – tipo de mutación, uno de:
    • "attributes": atributo modificado,
    • "characterData": dato modificado, usado para nodos de texto,
    • "childList": elementos hijos agregados o quitados,
  • target – dónde ocurrió el cambio: un elemento para "attributes", o un nodo de texto para "characterData", o un elemento para una mutación de "childList",
  • addedNodes/removedNodes – nodos que fueron agregados o quitados,
  • previousSibling/nextSibling – los nodos “hermanos”, previos y siguientes a los nodos agregados y quitados,
  • attributeName/attributeNamespace – el nombre o namespace (para XML) del atributo cambiado,
  • oldValue – el valor previo, solamente cambios de atributo o cambios de texto si se establece la opción correspondiente attributeOldValue/characterDataOldValue.

Por ejemplo, aquí hay un <div> con un atributo contentEditable. Ese atributo nos permite poner el foco en él y editarlo.

<div contentEditable id="elem">Click and <b>edit</b>, please</div>

<script>
let observer = new MutationObserver(mutationRecords => {
  console.log(mutationRecords); // console.log(los cambios)
});

// observa todo exceptuando atributos
observer.observe(elem, {
  childList: true, // observa hijos directos
  subtree: true, // y descendientes inferiores también
  characterDataOldValue: true // pasa el dato viejo al callback
});
</script>

Si ejecutamos este código en el navegador, el foco en el <div> dado y el cambio en texto dentro de <b>edit</b>, console.log mostrará una mutación:

mutationRecords = [{
  type: "characterData",
  oldValue: "edit",
  target: <text node>,
  // otras propiedades vacías
}];

Si hacemos operaciones de edición más complejas, como eliminar el <b>edit</b>, el evento de mutación puede contener múltiples registros de mutación:

mutationRecords = [{
  type: "childList",
  target: <div#elem>,
  removedNodes: [<b>],
  nextSibling: <text node>,
  previousSibling: <text node>
  // otras propiedades vacías
}, {
  type: "characterData"
  target: <text node>
  // ...detalles de mutación dependen de cómo el navegador maneja tal eliminación
  // puede unir dos nodos de texto adyacentes "edit " y ", please" en un nodo
  // o puede dejarlos como nodos de texto separados
}];

Así, MutationObserver permite reaccionar a cualquier cambio dentro del subárbol DOM.

Uso para integración

¿Cuándo puede ser práctico esto?

Imagina la situación en la que necesitas añadir un script de terceros que contiene funcionalidad útil, pero también hace algo no deseado, por ejemplo añadir publicidad <div class="ads">Unwanted ads</div>.

Naturalmente, el script de terceros no proporciona mecanismos para eliminarla.

Usando MutationObserver podemos detectar cuándo aparece el elemento no deseado en el DOM y eliminarlo.

Hay otras situaciones, como cuando un script de terceros agrega algo a nuestro documento y queremos detectarlo para adaptar la página, ajustar el tamaño de algún elemento dinámicamente, etc.

MutationObserver permite implementarlo.

Uso para arquitectura

Hay también situaciones donde MutationObserver es útil desde el punto de vista de la arquitectura.

Digamos que estamos haciendo un sitio web acerca de programación. Naturalmente, los artículos y otros materiales pueden contener fragmentos de código.

Un fragmento de código en HTML se ve así:

...
<pre class="language-javascript"><code>
  // aquí el código
  let hello = "world";
</code></pre>
...

Para mejorar la legibilidad y, al mismo tiempo, embellecer el sitio, usaremos una librería de resaltado de sintaxis de JavaScript, como Prism.js. Para obtener resaltado de sintaxis para el fragmento anterior en Prism, se llama a Prism.highlightElement(pre), que examina el contenido de esos elementos pre y les agrega etiquetas y estilos especiales para aplicar el resaltado de sintaxis con colores, similar al que ves en los ejemplos de esta página.

¿Cuándo debemos ejecutar exactamente ese método de resaltado? Podemos hacerlo en el evento DOMContentLoaded o colocar el script al final de la página. En cuanto el DOM esté listo, buscamos los elementos pre[class*="language"] y llamamos a Prism.highlightElement para cada uno:

// resaltar todos los fragmentos de código en la página
document.querySelectorAll('pre[class*="language"]').forEach(elem => Prism.highlightElement(elem));

Todo es simple hasta ahora, ¿verdad? Buscamos fragmentos de código en HTML y los resaltamos.

Continuemos. Digamos que vamos a buscar dinámicamente material desde un servidor. Estudiaremos métodos para ello más adelante en el tutorial. Por ahora solamente importa que buscamos un artículo HTML desde un servidor web y lo mostramos bajo demanda:

let article = /* busca contenido nuevo desde un servidor */
articleElem.innerHTML = article;

El nuevo elemento HTML article puede contener fragmentos de código. Necesitamos llamar a Prism.highlightElement para ellos; de lo contrario, no se resaltarán.

¿Dónde y cuándo debemos llamar a Prism.highlightElement para un artículo cargado dinámicamente?

Podríamos agregar la llamada al código que carga un “article”, por ejemplo:

let article = /* busca contenido nuevo desde un servidor */
articleElem.innerHTML = article;

let snippets = articleElem.querySelectorAll('pre[class*="language-"]');
snippets.forEach(elem => Prism.highlightElement(elem));

… pero imagina que tenemos muchos lugares en el código donde cargamos contenido: artículos, cuestionarios, entradas de foros. ¿Necesitamos agregar la llamada al resaltado de sintaxis en todos ellos? No sería muy conveniente.

¿Y si el contenido lo carga un módulo de terceros? Por ejemplo, tenemos un foro escrito por otra persona que carga contenido dinámicamente, y queremos añadirle resaltado de sintaxis. A nadie le gusta modificar scripts de terceros.

Afortunadamente, hay otra opción.

Podemos usar MutationObserver para detectar automáticamente cuándo se insertan fragmentos de código en la página y resaltarlos.

Así, podemos manejar el resaltado de sintaxis desde un único lugar, sin necesidad de integrarlo en cada punto donde se carga contenido.

Demo de resaltado dinámico

Aquí el ejemplo funcionando.

Si ejecutas el código, este comienza a observar el elemento debajo y resalta cualquier fragmento de código que aparezca allí:

let observer = new MutationObserver(mutations => {

  for(let mutation of mutations) {
    // examine nodos nuevos, ¿hay algo para resaltar?

    for(let node of mutation.addedNodes) {
      // seguimos elementos solamente, saltamos los otros nodos (es decir nodos de texto)
      if (!(node instanceof HTMLElement)) continue;

      // verificamos que el elemento insertado sea un fragmento de código
      if (node.matches('pre[class*="language-"]')) {
        Prism.highlightElement(node);
      }

      // ¿o tal vez haya un fragmento de código en su sub-árbol?
      for(let elem of node.querySelectorAll('pre[class*="language-"]')) {
        Prism.highlightElement(elem);
      }
    }
  }

});

let demoElem = document.getElementById('highlight-demo');

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

Aquí, abajo, hay un elemento HTML y JavaScript que lo llena dinámicamente usando innerHTML.

Por favor ejecuta el código anterior (arriba, que observa aquel elemento) y luego el código de abajo. Verás cómo MutationObserver detecta y resalta el fragmento.

A demo-element with id="highlight-demo", run the code above to observe it.

El siguiente código llena su innerHTML, lo que causa que MutationObserver reaccione y resalte su contenido:

let demoElem = document.getElementById('highlight-demo');

// inserta contenido con fragmentos de código
demoElem.innerHTML = `A code snippet is below:
  <pre class="language-javascript"><code> let hello = "world!"; </code></pre>
  <div>Another one:</div>
  <div>
    <pre class="language-css"><code>.class { margin: 5px; } </code></pre>
  </div>
`;

Ahora tenemos un MutationObserver que puede rastrear todo el resaltado en los elementos observados del document entero. Podemos agregar o quitar fragmentos de código en el HTML sin siquiera pensar en ello.

Métodos adicionales

Hay un método para detener la observación del nodo:

  • observer.disconnect() – detiene la observación.

Cuando detenemos la observación, algunos cambios todavía podrían quedar sin ser procesados por el observador. En tales casos usamos

  • observer.takeRecords() – obtiene una lista de registros de mutaciones sin procesar, aquellos que ocurrieron pero el callback no manejó.

Estos métodos pueden ser usados juntos, como esto:

// obtener una lista de mutaciones sin procesar
// debe ser llamada antes de la desconexión,
// si te interesa las posibles mutaciones recientes sin manejar
let mutationRecords = observer.takeRecords();

// detener el rastreo de cambios
observer.disconnect();
...
Los registros devueltos por observer.takeRecords() son eliminados de la cola de procesamiento

El callback no será llamado en registros devueltos por observer.takeRecords().

Interacción con la recolección de basura

Los observadores usan internamente referencias débiles a nodos. Esto es: si un nodo es quitado del DOM y se hace inalcanzable, se vuelve basura para ser recolectada.

El mero hecho de que un nodo DOM sea observado no evita la recolección de basura.

Resumen

MutationObserver puede reaccionar a cambios en el DOM: atributos, contenido de texto y la adición o eliminación de elementos.

Podemos usarlo para rastrear cambios introducidos por otras partes de nuestro código o para integrarlo con scripts de terceros.

MutationObserver puede rastrear cualquier cambio. Las opciones de config permiten especificar qué se va a observar. Esto sirve para optimizar el rendimiento y evitar llamadas innecesarias al callback.

Mapa del Tutorial

Comentarios

lea esto antes de comentar…
  • Si tiene sugerencias sobre qué mejorar, por favor enviar una propuesta de GitHub o una solicitud de extracción en lugar de comentar.
  • Si no puede entender algo en el artículo, por favor explique.
  • Para insertar algunas palabras de código, use la etiqueta <code>, para varias líneas – envolverlas en la etiqueta <pre>, para más de 10 líneas – utilice una entorno controlado (sandbox) (plnkr, jsbin, codepen…)