Back

Cuándo Ejecutar tu Código: Eventos de Carga de Página Explicados

Cuándo Ejecutar tu Código: Eventos de Carga de Página Explicados

¿Cuándo debería ejecutarse realmente tu JavaScript? Es una pregunta que todo desarrollador frontend enfrenta, ya sea manipulando el DOM en JavaScript vanilla o gestionando ciclos de vida de componentes en React. La respuesta depende de comprender los eventos de carga de página del navegador y elegir el gancho adecuado para tus necesidades específicas.

Puntos Clave

  • DOMContentLoaded se dispara cuando se completa el análisis del HTML, mientras que load espera a todos los recursos
  • Los navegadores modernos utilizan las APIs de Page Visibility y Lifecycle en lugar de eventos unload poco confiables
  • React y otros frameworks manejan el timing a través de métodos de ciclo de vida de componentes
  • Verifica document.readyState para evitar perder eventos que ya se dispararon

Comprendiendo el Ciclo de Vida Clásico del Navegador

El navegador dispara eventos en puntos específicos durante la carga de la página. Saber cuándo se dispara cada uno—y qué está disponible en ese momento—determina dónde debe ir tu código.

DOMContentLoaded vs load: La Distinción Crítica

DOMContentLoaded se dispara cuando el HTML está completamente analizado y el árbol DOM está construido. Los recursos externos como imágenes, hojas de estilo e iframes todavía se están cargando. Esta es tu primera oportunidad para consultar y manipular elementos del DOM de forma segura:

document.addEventListener('DOMContentLoaded', () => {
    // DOM está listo, pero las imágenes podrían estar cargándose aún
    const button = document.querySelector('#submit');
    button.addEventListener('click', handleSubmit);
});

El evento load espera a todo—imágenes, hojas de estilo, iframes y otros recursos externos. Úsalo cuando necesites información completa de los recursos:

window.addEventListener('load', () => {
    // Todos los recursos cargados - las dimensiones de imagen están disponibles
    const img = document.querySelector('#hero');
    console.log(`Tamaño de imagen: ${img.naturalWidth}x${img.naturalHeight}`);
});

Cómo los Scripts Afectan el Timing

Las etiquetas <script> regulares bloquean DOMContentLoaded—el navegador debe ejecutarlas antes de continuar. Sin embargo, los scripts con defer se ejecutan después del análisis del DOM pero antes de que se dispare DOMContentLoaded. Los scripts con async se cargan en paralelo y se ejecutan inmediatamente cuando se descargan, potencialmente antes o después de DOMContentLoaded.

Para recursos específicos de elementos, usa eventos load individuales:

const img = new Image();
img.addEventListener('load', () => console.log('Imagen lista'));
img.src = 'photo.jpg';

Enfoques Modernos: document.readyState y Más Allá

Usando document.readyState

En lugar de esperar que tu event listener se adjunte a tiempo, verifica el estado actual:

function initialize() {
    // Tu código de inicialización
}

if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initialize);
} else {
    // DOMContentLoaded ya se disparó
    initialize();
}

Los tres estados son:

  • 'loading' - El documento se está analizando
  • 'interactive' - Análisis completo, DOMContentLoaded a punto de dispararse
  • 'complete' - Todos los recursos cargados

El $(document).ready() de jQuery era esencialmente un wrapper cross-browser para este patrón. Hoy en día, las APIs nativas manejan esto de forma confiable en todos los navegadores.

Page Visibility y Lifecycle API: La Nueva Realidad

Por Qué beforeunload y unload Son Problemáticos

Los navegadores modernos optimizan agresivamente el rendimiento a través de características como el back-forward cache (bfcache). Las páginas que entran en bfcache no se descargan—se suspenden. Esto significa que los eventos beforeunload y unload no se disparan de forma confiable cuando los usuarios navegan fuera. Además, los navegadores ahora limitan o ignoran mensajes personalizados en diálogos beforeunload para prevenir abusos.

La Alternativa de Page Visibility API

En lugar de eventos unload poco confiables, usa la Page Visibility API para responder cuando los usuarios cambian de pestaña o minimizan el navegador:

document.addEventListener('visibilitychange', () => {
    if (document.hidden) {
        // Página está oculta - pausar operaciones costosas
        pauseVideoPlayback();
        throttleWebSocket();
    } else {
        // Página visible nuevamente
        resumeOperations();
    }
});

Estados del Ciclo de Vida de Página

La Page Lifecycle API extiende esto con estados como frozen (pestaña suspendida para ahorrar memoria) y terminated:

document.addEventListener('freeze', () => {
    // Guardar estado - la pestaña podría descartarse
    localStorage.setItem('appState', JSON.stringify(state));
});

document.addEventListener('resume', () => {
    // Restaurar después de estar congelado
    hydrateState();
});

React useEffect vs DOMContentLoaded

En React y otros frameworks modernos, rara vez usas DOMContentLoaded directamente. Los métodos de ciclo de vida de componentes manejan el timing de inicialización:

// Componente React
import { useEffect } from 'react';

function MyComponent() {
    useEffect(() => {
        // Se ejecuta después de que el componente se monta y el DOM se actualiza
        // Timing similar a DOMContentLoaded para este componente
        initializeThirdPartyLibrary();
        
        return () => {
            // Limpieza al desmontar
            cleanup();
        };
    }, []); // Array de dependencias vacío = ejecutar una vez después del montaje
    
    return <div>Contenido del componente</div>;
}

Para Next.js u otros frameworks SSR, el código en useEffect se ejecuta solo en el cliente después de la hidratación—el framework maneja la complejidad del timing de ejecución servidor vs. cliente.

Eligiendo el Gancho Correcto

Para JavaScript vanilla:

  • Manipulación del DOM: Usa DOMContentLoaded
  • Código dependiente de recursos: Usa el evento window load
  • Persistencia de estado: Usa Page Visibility API
  • Verificar estado actual: Usa document.readyState

Para SPAs y frameworks:

  • Inicialización de componentes: Usa el ciclo de vida del framework (useEffect, mounted, etc.)
  • Cambios de ruta: Usa eventos del router
  • Background/foreground: Aún usa Page Visibility API

Evita estos patrones:

  • No confíes en beforeunload/unload para operaciones críticas
  • No uses DOMContentLoaded en componentes React
  • No asumas que los scripts se ejecutan en una carga de página fresca (considera bfcache)

Conclusión

Los eventos de carga de página en JavaScript han evolucionado más allá de los simples manejadores DOMContentLoaded y load. Aunque estos eventos clásicos siguen siendo esenciales para JavaScript vanilla, el desarrollo moderno requiere comprender Page Visibility, Lifecycle APIs y patrones específicos de frameworks. Elige tu estrategia de inicialización basándote en qué recursos necesitas y si estás trabajando con una página renderizada en servidor o construyendo una SPA completa. Lo más importante, no confíes en patrones obsoletos como eventos unload—adopta las APIs modernas construidas para aplicaciones web contemporáneas.

Preguntas Frecuentes

Los scripts defer se ejecutan en orden después del análisis del DOM pero antes de DOMContentLoaded. Los scripts async se ejecutan inmediatamente cuando se descargan, potencialmente interrumpiendo el análisis. Usa defer para scripts que necesitan acceso al DOM y async para scripts independientes como analytics.

Aunque window.onload funciona, se prefiere addEventListener porque permite múltiples manejadores y no sobrescribirá los existentes. El evento load en sí mismo sigue siendo útil cuando necesitas que todos los recursos estén completamente cargados antes de ejecutar código.

Usa useEffect con un array de dependencias vacío para inicialización del lado del cliente. Esto se ejecuta después de que la hidratación se completa. Para código del lado del servidor, usa métodos específicos del framework como getServerSideProps o getStaticProps en lugar de eventos del navegador.

Los navegadores modernos cachean páginas y pueden no disparar beforeunload cuando los usuarios navegan fuera. Usa la Page Visibility API en su lugar para detectar cuando los usuarios se van, y persiste datos críticos continuamente en lugar de al salir.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay