API MutationObserver de JavaScript
Aprende la API MutationObserver de JavaScript: cómo observar cambios en el DOM, los métodos observe/disconnect/takeRecords, MutationRecords, patrones y ejemplos.
La API MutationObserver en JavaScript te permite observar una parte del DOM y ejecutar un callback cada vez que algo cambia dentro de él: se añade o elimina un nodo, se edita un atributo o se actualiza el contenido de texto. A diferencia de los obsoletos mutation events que reemplazó, MutationObserver agrupa los cambios y los entrega de forma asíncrona, de modo que no bloquea la página ni dispara un evento separado por cada pequeña edición.
Esta guía cubre qué puedes observar, los tres métodos que expone cada observer, la estructura de un MutationRecord, casos de uso del mundo real, errores comunes y un ejemplo interactivo que puedes ejecutar.
¿Cuándo usarías un MutationObserver?
Recurres a un MutationObserver cuando el contenido que no controlas cambia y necesitas reaccionar a ello:
- Contenido de terceros / inyectado — un widget, anuncio o bloque renderizado por un CMS aparece en el DOM y necesitas aplicarle estilos o mejoras.
- Detectar cuándo un elemento finalmente existe — esperar a que un nodo que un framework renderiza más tarde, en lugar de sondear con
setInterval. - Sincronizar la UI con cambios de atributos — reaccionar cuando
class,style,disabledo un atributodata-*cambia en un elemento al que no puedes añadir un listener. - Cambio automático de tamaño o recálculo de medidas — recalcular el layout cuando se insertan hijos en un contenedor.
- Mantener un modelo sincronizado con texto de
contenteditable.
Si solo necesitas saber cuándo un elemento entra o sale del viewport, usa un IntersectionObserver en su lugar — está diseñado específicamente para ese propósito y es más eficiente.
Los tres métodos
Cada instancia de observer expone exactamente tres métodos:
| Método | Qué hace |
|---|---|
observe(target, options) | Comienza a observar target usando un object de opciones. Llámalo de nuevo con un target diferente para observar más de un nodo con el mismo observer. |
disconnect() | Deja de observar todos los targets. El callback ya no se ejecutará. Llama siempre a este método cuando hayas terminado para evitar fugas de memoria. |
takeRecords() | Devuelve de forma síncrona (y limpia) cualquier MutationRecord pendiente que aún no se haya entregado al callback. Útil justo antes de disconnect() para no perder el último lote. |
El object options que se pasa a observe() debe habilitar al menos uno de childList, attributes o characterData, o la llamada lanzará un TypeError.
| Opción | Significado |
|---|---|
childList | Observar nodos hijos directos añadidos o eliminados. |
attributes | Observar cambios en atributos. |
characterData | Observar cambios en los datos de los nodos de texto. |
subtree | Extender lo anterior a todos los descendientes, no solo al target. |
attributeOldValue | Registrar el valor anterior del atributo (implica attributes). |
characterDataOldValue | Registrar el valor de texto anterior (implica characterData). |
attributeFilter | Array de nombres de atributos a observar — ignorar el resto. |
Ejemplo de Mutation Observer
Este es un ejemplo básico para ayudarte a ver cómo funciona un Mutation Observer indicando visualmente los cambios en el DOM.
<!DOCTYPE html>
<html>
<head>
<title>Exploring DOM Changes: Live Examples with Mutation Observers</title>
</head>
<body>
<div id="target" style="background-color: lightgray; padding: 10px;">
Watch this space for changes!
</div>
<button style="margin-top: 10px;" onclick="addNewElement(); changeAttribute();">Add New Element and Change Color</button>
<div id="log" style="margin-top: 20px;"></div>
<script>
// Get the element to observe
const targetNode = document.getElementById('target');
// Define configurations for the observer
const config = { attributes: true, childList: true, subtree: true, attributeOldValue: true };
// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer) {
for (const mutation of mutationsList) {
const message = document.createElement('p');
if (mutation.type === 'childList') {
message.textContent = 'A child node has been added or removed.';
message.style.color = 'green';
} else if (mutation.type === 'attributes') {
message.textContent = 'The ' + mutation.attributeName + ' attribute was modified.';
message.style.color = 'blue';
}
document.getElementById('log').appendChild(message);
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
// Function to add new elements
function addNewElement() {
const newElement = document.createElement('div');
newElement.textContent = 'New element added!';
targetNode.appendChild(newElement);
}
// Function to change attributes
function changeAttribute() {
const currentColor = targetNode.style.backgroundColor;
targetNode.style.backgroundColor = currentColor === 'lightgray' ? 'lightblue' : 'lightgray';
}
</script>
</body>
</html>Este ejemplo muestra cómo usar un Mutation Observer para detectar y responder a los cambios en el Modelo de Objetos del Documento (DOM) de una página web. Esto es lo que hace cada parte del JavaScript y lo que puedes esperar ver al interactuar con el ejemplo:
- Configurar el Mutation Observer:
- Nodo target: Es el elemento del DOM que quieres observar. En este caso, es el
divcon IDtarget. - Configuraciones: Especifican qué tipos de cambios quieres monitorizar:
attributes: El observer buscará cambios en los atributos (como style o class).childList: Comprobará la adición o eliminación de elementos hijo (como nuevos divs que se añaden).subtree: Esto garantiza que el observer compruebe no solo el elemento target, sino también sus descendientes. Ten en cuenta quesubtreesolo tiene efecto si también se habilitachildListoattributes.attributeOldValue: Esto registra el valor anterior de cualquier atributo que se modifique (útil para rastrear cambios).
- Nodo target: Es el elemento del DOM que quieres observar. En este caso, es el
- Definir una función callback:
- Esta función se ejecuta cada vez que el observer detecta un cambio según las configuraciones establecidas.
- Recorre todas las mutaciones detectadas y crea un mensaje de registro para cada una:
- Si se añade o elimina un elemento hijo, registra "A child node has been added or removed." en texto verde.
- Si se cambia un atributo (como el color de fondo), registra "The
mutation.attributeNameattribute was modified." en texto azul.
- Instancia del Observer:
- El Mutation Observer se crea y se vincula a la función callback.
- Comenzar a observar:
- El observer comienza a monitorizar el
divtargetpara detectar cualquier cambio especificado en las configuraciones.
- El observer comienza a monitorizar el
- Funciones interactivas:
- Añadir nuevo elemento: Activada por un clic en el botón, esta función añade un nuevo div con el texto "New element added!" dentro del
divtarget. - Cambiar atributo: También activada por el mismo clic de botón, esta función alterna el color de fondo del
divtargetentre 'lightgray' y 'lightblue'. Nota: Aunque elonclicken línea funciona para este ejemplo, se recomienda usaraddEventListenerpara una separación de código más limpia en producción.
- Añadir nuevo elemento: Activada por un clic en el botón, esta función añade un nuevo div con el texto "New element added!" dentro del
Resultados esperados:
- Añadir un nuevo elemento:
- Cada vez que haces clic en el botón, se añade un nuevo div. Esto activa la comprobación
childListdel observer y verás un mensaje verde que dice "A child node has been added or removed."
- Cada vez que haces clic en el botón, se añade un nuevo div. Esto activa la comprobación
- Cambiar un atributo:
- El mismo clic de botón cambiará el color de fondo del
divtarget. Esto activa la comprobación de atributos del observer. Verás un mensaje azul que indica qué atributo cambió ("The style attribute was modified.").
- El mismo clic de botón cambiará el color de fondo del
Este ejemplo demuestra de forma efectiva cómo se pueden usar los Mutation Observers para monitorizar y registrar cambios en el DOM, proporcionando retroalimentación en tiempo real sobre lo que ocurre en la página web.
Leer un MutationRecord
El callback recibe un array de objetos MutationRecord — uno por cada cambio detectado. Las propiedades más útiles son:
type—"childList","attributes"o"characterData".target— el nodo al que afectó la mutación.addedNodes/removedNodes—NodeLists de nodos insertados/eliminados (parachildList).attributeName— el atributo modificado (paraattributes).oldValue— el valor anterior, pero solo si se habilitóattributeOldValueocharacterDataOldValue.
const observer = new MutationObserver((records) => {
for (const record of records) {
if (record.type === "attributes") {
console.log(`${record.attributeName} changed from "${record.oldValue}"`);
} else if (record.type === "childList") {
console.log(`+${record.addedNodes.length} / -${record.removedNodes.length} nodes`);
}
}
});Un patrón práctico: esperar a que aparezca un elemento
Un uso real muy común es resolver una Promise en el momento en que un nodo aparece en el DOM — mucho mejor que el sondeo. El observer se desconecta a sí mismo tan pronto como encuentra el elemento:
function waitForElement(selector) {
return new Promise((resolve) => {
const existing = document.querySelector(selector);
if (existing) return resolve(existing);
const observer = new MutationObserver(() => {
const el = document.querySelector(selector);
if (el) {
observer.disconnect(); // stop watching once found
resolve(el);
}
});
observer.observe(document.body, { childList: true, subtree: true });
});
}
// Usage with async/await:
// const card = await waitForElement(".lazy-card");Esto se combina naturalmente con async/await y las Promises.
Errores comunes y buenas prácticas
- El callback es asíncrono y se agrupa en lotes. Las mutaciones se ponen en cola como microtareas y se entregan tras finalizar el script actual — consulta microtareas y el event loop. No obtendrás un registro por cada cambio individual; los recibirás agrupados.
- Tu callback se ejecuta después del cambio. Se te notifica de lo que ya ocurrió — no puedes cancelar ni prevenir una mutación como harías con
preventDefault()en un evento. oldValuerequiere activación explícita. Si leesrecord.oldValuesin habilitarattributeOldValue/characterDataOldValue, seránull.- Evita mutar el subárbol observado dentro del callback a menos que lo hagas intencionadamente — hacerlo puede generar más registros y crear un bucle de retroalimentación.
- Siempre llama a
disconnect(). Un observer activo mantiene su target alcanzable, por lo que olvidar desconectarlo provoca fugas de memoria. Desconecta cuando el componente se desmonte o el trabajo esté terminado. - Llama a
takeRecords()antes de desconectar si el último lote es importante —disconnect()descarta los registros no entregados.
Conclusión
Los Mutation Observers son una parte fundamental del toolkit de JavaScript, que ofrece soluciones dinámicas para gestionar los cambios del DOM de forma eficiente. Permiten a los desarrolladores construir aplicaciones web responsivas e interactivas que reaccionan de forma fluida a las interacciones del usuario y a las modificaciones programáticas del DOM. Si bien son poderosos, es fundamental usar los Mutation Observers con criterio para mantener un rendimiento óptimo y una buena experiencia de usuario. Seleccionando cuidadosamente qué mutaciones observar, minimizando la carga en los callbacks de mutación y llamando a observer.disconnect() cuando el observer ya no se necesita para prevenir fugas de memoria, los desarrolladores pueden aprovechar los Mutation Observers para mejorar la funcionalidad del sitio sin comprometer la eficiencia. Entender y aplicar estos principios permite crear interfaces web avanzadas y amigables para el usuario que destacan en el panorama digital moderno.