Eventos del ciclo de vida de una página en JavaScript
Aprende los eventos del ciclo de vida DOMContentLoaded, load, beforeunload y unload en JavaScript: cuándo se disparan, sus diferencias y alternativas modernas.
Cada página que abre un navegador pasa por una secuencia predecible de etapas: el HTML se analiza, los recursos externos se descargan, el usuario interactúa y, finalmente, la página se destruye. JavaScript expone esta secuencia a través de cuatro eventos del ciclo de vida de página — DOMContentLoaded, load, beforeunload y unload. Saber exactamente cuándo se dispara cada uno te permite ejecutar código de inicialización en el momento más temprano posible, diferir trabajo costoso hasta que todo esté listo y proteger al usuario de perder datos no guardados.
Esta guía cubre qué significa cada evento, el orden en que se disparan, los errores comunes y los reemplazos modernos para los eventos que los navegadores ahora desaconsejan. Todos los ejemplos a continuación son ejecutables — pégalos en un archivo HTML o ábrelos en el editor integrado.
El ciclo de vida de la página de un vistazo
Los eventos se disparan en un orden fijo. Los dos primeros ocurren cuando la página cobra vida; los dos últimos ocurren cuando el usuario la abandona:
| Evento | Objetivo | Cuándo se dispara | Uso típico |
|---|---|---|---|
DOMContentLoaded | document | El HTML se ha analizado completamente; los scripts se han ejecutado; las imágenes/estilos pueden seguir cargando | Inicializar la interfaz, adjuntar listeners, consultar el DOM |
load | window | La página y todos los sub-recursos (imágenes, hojas de estilo, iframes) han terminado | Leer tamaños de imágenes, ejecutar código que necesita que todo esté presente |
beforeunload | window | El usuario está a punto de navegar fuera de la página | Advertir sobre cambios no guardados |
unload | window | La página está siendo destruida (obsoleto) | Limpieza heredada / analíticas |
Un punto clave: DOMContentLoaded es el evento que querrás usar la mayor parte del tiempo. Se dispara mucho antes que load, por lo que adjuntar tu código a él hace que la página sea interactiva más rápido.
Análisis profundo del evento DOMContentLoaded
El evento DOMContentLoaded se dispara tan pronto como el documento HTML ha sido completamente analizado, lo que normalmente ocurre mucho antes de que se hayan cargado imágenes, hojas de estilo y otros recursos externos. En este punto el árbol del DOM está completo, por lo que document.getElementById, querySelector y similares encontrarán todos los elementos escritos en el HTML.
Dado que no espera a las imágenes ni al CSS, deberías usar DOMContentLoaded cuando tu script solo necesita la estructura del DOM — que es el caso más común con diferencia.
Cómo los scripts afectan a DOMContentLoaded
El navegador pausa el análisis del HTML cuando encuentra una etiqueta <script> ordinaria, ejecuta el script y solo entonces continúa. Esto significa que DOMContentLoaded espera a los scripts síncronos. Hay dos matices importantes:
<script async>se ejecuta en cuanto se descarga y no bloqueaDOMContentLoaded.<script defer>se ejecuta después de que el documento ha sido analizado pero antes de que se dispareDOMContentLoaded, en el orden del documento.
Si controlas el momento de ejecución de los scripts con estos atributos, consulta scripts: async, defer para una visión completa.
Verificar el estado con document.readyState
Si tu código podría ejecutarse después de que el DOM ya haya sido analizado (por ejemplo, un script añadido dinámicamente), el listener nunca se dispararía porque el evento ya ocurrió. Protégete contra esto con document.readyState:
function onReady() {
console.log('DOM is ready, current state:', document.readyState);
}
if (document.readyState === 'loading') {
// Still parsing — wait for the event.
document.addEventListener('DOMContentLoaded', onReady);
} else {
// 'interactive' or 'complete' — DOM is already ready.
onReady();
}readyState pasa por tres valores: "loading" mientras se analiza, "interactive" una vez que el DOM está listo (es cuando se dispara DOMContentLoaded), y "complete" una vez que todo, incluidos los recursos, ha cargado (es cuando se dispara load).
Ejemplo interactivo: DOMContentLoaded en acción
Para ver DOMContentLoaded en acción, el siguiente ejemplo actualiza el contenido de un elemento div una vez que el documento HTML ha sido completamente analizado pero antes de que la página entera esté completamente cargada.
<!DOCTYPE html>
<html lang="en">
<head>
<title>DOMContentLoaded Example</title>
</head>
<body>
<div id="output">Waiting for DOM...</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('output').innerHTML = 'DOM fully loaded and parsed!'
});
</script>
</body>
</html>Este ejemplo puede pegarse en cualquier archivo HTML y visualizarse en un navegador para observar con qué rapidez se dispara DOMContentLoaded en comparación con la carga completa de la página.
Explorando el evento load
El evento load es esencial para operaciones que requieren que la página web entera esté completamente cargada, incluyendo todos los recursos dependientes como imágenes y hojas de estilo. Cuando se dispara load, cada imagen tiene sus dimensiones reales, las fuentes están aplicadas y los iframes están cargados — por lo que este es el lugar correcto para el código que mide la página renderizada.
El inconveniente es el tiempo: una sola imagen grande o un widget de terceros lento retrasa load para toda la página. Por eso deberías reservar load para el trabajo que genuinamente depende de los recursos, y mantener la inicialización ordinaria en DOMContentLoaded. Para la carga y gestión de errores de recursos individuales (en lugar de la página completa), consulta resource loading: onload and onerror.
Ejemplo interactivo: demostración del evento load
Aquí creamos un ejemplo donde se muestra un mensaje solo después de que todo en la página esté completamente cargado. Como puedes ver, el evento DOMContentLoaded siempre ocurre antes que el evento load.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Load Event Example</title>
</head>
<body>
<div>You see first triggered event on top of the other.</div>
<script>
window.addEventListener('load', function() {
const newDiv = document.createElement("div");
newDiv.innerHTML = 'loaded event happened!'
document.body.append(newDiv);
});
window.addEventListener('DOMContentLoaded', function() {
const newDiv = document.createElement("div");
newDiv.innerHTML = 'DOMContentLoaded event happened!'
document.body.append(newDiv);
});
</script>
</body>
</html>Copia y prueba este código en tu propio entorno HTML para ver la diferencia de tiempo entre DOMContentLoaded y load.
Gestión del evento beforeunload
El evento beforeunload es increíblemente útil para prevenir la pérdida de datos, advirtiendo a los usuarios antes de que naveguen fuera de una página que puede tener cambios no guardados. Cuando un manejador establece event.returnValue, el navegador muestra un diálogo de confirmación genérico ("¿Abandonar el sitio? Es posible que los cambios que realizaste no se guarden").
Algunas reglas que los navegadores aplican actualmente, que vale la pena conocer antes de depender de esto:
- El mensaje personalizado se ignora. Para evitar el spam, los navegadores muestran su propio texto independientemente de la cadena que asignes. Establecer
returnValueen cualquier valor no vacío (o llamar aevent.preventDefault()) es suficiente para activar el diálogo. - El diálogo solo aparece si el usuario ha interactuado con la página (hecho clic, escrito, etc.). Una página que el usuario nunca tocó no puede bloquear la navegación.
- Registra el manejador solo cuando haya cambios realmente no guardados, y elimínalo una vez que los datos estén guardados. Un manejador
beforeunloadpermanente molesta a los usuarios y puede afectar el rendimiento de la caché de navegación adelante/atrás.
Un patrón más robusto agrega y elimina el listener basándose en una bandera de modificación:
let isDirty = false;
function beforeUnloadHandler(event) {
event.preventDefault();
// Required for some browsers to show the dialog.
event.returnValue = '';
}
function markDirty() {
if (!isDirty) {
isDirty = true;
window.addEventListener('beforeunload', beforeUnloadHandler);
}
}
function markSaved() {
isDirty = false;
window.removeEventListener('beforeunload', beforeUnloadHandler);
}Ejemplo interactivo: implementación de la confirmación beforeunload
Este ejemplo muestra al usuario un diálogo de confirmación cuando intenta abandonar la página, ayudando a prevenir la pérdida accidental de datos.
<!DOCTYPE html>
<html lang="en">
<head>
<title>beforeunload Example</title>
</head>
<body>
<p>Now if you want to exit this page, a confirmation alert will be shown as a result of the <code>beforeunload</code> event.</p>
<script>
window.addEventListener('beforeunload', function(event) {
event.returnValue = '';
});
</script>
</body>
</html>Prueba este código entrando en la página "pruébalo tú mismo" e intentando navegar a otra página.
Uso del evento unload
The unload event is deprecated and you won't be able to use it in most modern browsers. It is also considered a bad practice. Therefore this part is only for better information about legacy codes.
Aunque menos utilizado debido a las restricciones de los navegadores modernos y el auge de las aplicaciones de una sola página, el evento unload históricamente se ejecutaba cuando una página estaba siendo destruida — útil para limpiar recursos o enviar un ping de analíticas final. El problema es que unload (y beforeunload) impide que el navegador almacene la página en la caché de navegación adelante/atrás (bfcache), lo que ralentiza la navegación con el botón atrás para todos.
Alternativas modernas: visibilitychange y pagehide
En lugar de unload, escucha visibilitychange y trata la transición a "hidden" como el último momento fiable para persistir el estado. Esto se dispara de forma consistente en escritorio y móvil, incluso cuando el usuario cambia de pestaña o cierra la aplicación:
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
// Last reliable point to save state.
console.log('Page hidden — flush analytics / save draft here');
}
});Para enviar datos al salir, usa navigator.sendBeacon(), que encola una solicitud que sobrevive al cierre de la página:
function sendStats(data) {
navigator.sendBeacon('/log', JSON.stringify(data));
}Ejemplo interactivo: uso del evento unload
Este código muestra un mensaje de alerta cuando la página está siendo descargada. Está obsoleto como se mencionó y no funciona en la mayoría de los navegadores modernos — prefiere visibilitychange en su lugar.
<!DOCTYPE html>
<html lang="en">
<head>
<title>unload Example</title>
</head>
<body>
<script>
window.addEventListener('unload', function(event) {
alert('Page is unloading...');
// Perform cleanup tasks or analytics here
});
</script>
</body>
</html>Conclusión
Cada uno de estos eventos sirve a una fase específica en el ciclo de vida de una página web, desde la carga inicial hasta la descarga final. La guía práctica es breve:
- Ejecuta la mayor parte de la inicialización en
DOMContentLoaded— se dispara primero y el DOM está listo. - Usa
loadsolo cuando dependas de que las imágenes, fuentes u otros sub-recursos estén completamente presentes. - Usa
beforeunloadpara proteger los cambios no guardados, y agrega el listener solo mientras los datos estén sin guardar. - Evita
unload; opta porvisibilitychangemásnavigator.sendBeacon()en su lugar.
Para profundizar más, explora estos capítulos relacionados:
- Introducción a los eventos del navegador
- Scripts: async, defer
- Carga de recursos: onload y onerror
- Entorno del navegador y especificaciones