¿Puedo ejecutar javascript antes de que se cargue toda la página?

Respuestas:

186

No sólo puede usted, pero usted tiene que hacer un esfuerzo especial no a si no quieren. :-)

Cuando el navegador encuentra una scriptetiqueta clásica al analizar el HTML, deja de analizar y se lo entrega al intérprete de JavaScript, que ejecuta el script. El analizador no continúa hasta que se completa la ejecución del script (porque el script puede hacer document.writellamadas al marcado de salida que el analizador debería manejar).

Ese es el comportamiento predeterminado, pero tiene algunas opciones para retrasar la ejecución del script:

  1. Utilice módulos de JavaScript. Un type="module"script se aplaza hasta que el HTML se haya analizado por completo y se haya creado el DOM inicial. Esta no es la razón principal para usar módulos, pero es una de las razones:

    <script type="module" src="./my-code.js"></script>
    <!-- Or -->
    <script type="module">
    // Your code here
    </script>

    El código se obtendrá (si está separado) y se analizará en paralelo con el análisis de HTML, pero no se ejecutará hasta que finalice el análisis de HTML. (Si el código de su módulo está en línea en lugar de en su propio archivo, también se aplaza hasta que se complete el análisis de HTML).

    Esto no estaba disponible cuando escribí esta respuesta por primera vez en 2010, pero aquí en 2020, todos los principales navegadores modernos admiten módulos de forma nativa, y si necesita admitir navegadores más antiguos, puede usar paquetes como Webpack y Rollup.js.

  2. Utilice el deferatributo en una etiqueta de secuencia de comandos clásica:

    <script defer src="./my-code.js"></script>

    Al igual que con el módulo, el código en se my-code.jsbuscará y analizará en paralelo con el análisis de HTML, pero no se ejecutará hasta que finalice el análisis de HTML. Sin embargo , deferno funciona con el contenido de la escritura en línea, sólo con archivos externos referenciados a través src.

  3. No creo que sea lo que desea, pero puede usar el asyncatributo para decirle al navegador que busque el código JavaScript en paralelo con el análisis de HTML, pero luego ejecútelo lo antes posible, incluso si el análisis de HTML no está completo. . Puede ponerlo en una type="module"etiqueta o usarlo en lugar de deferuna scriptetiqueta clásica .

  4. Coloque la scriptetiqueta al final del documento, justo antes de la </body>etiqueta de cierre :

    <!doctype html>
    <html>
    <!-- ... -->
    <body>
    <!-- The document's HTML goes here -->
    <script type="module" src="./my-code.js"></script><!-- Or inline script -->
    </body>
    </html>

    De esa manera, aunque el código se ejecute tan pronto como se encuentre, todos los elementos definidos por el HTML anterior existen y están listos para ser utilizados.

    Solía ​​ser que esto causaba un retraso adicional en algunos navegadores porque no comenzarían a buscar el código hasta que scriptse encontrara la etiqueta, pero los navegadores modernos escanean con anticipación y comienzan a buscar previamente. Aún así, esta es la tercera opción en este punto, ambos módulos deferson mejores opciones.

La especificación tiene un diagrama útil que muestra unscript etiqueta, defer, async, type="module", y type="module" asyncy el momento de cuando el código JavaScript se capta y ejecute:

ingrese la descripción de la imagen aquí

A continuación, se muestra un ejemplo del comportamiento predeterminado, una scriptetiqueta sin formato:

.found {
    color: green;
}
<p>Paragraph 1</p>
<script>
    if (typeof NodeList !== "undefined" && !NodeList.prototype.forEach) {
        NodeList.prototype.forEach = Array.prototype.forEach;
    }
    document.querySelectorAll("p").forEach(p => {
        p.classList.add("found");
    });
</script>
<p>Paragraph 2</p>

(Vea mi respuesta aquí para obtener detalles sobre ese NodeListcódigo).

Cuando ejecutas eso, ves "Párrafo 1" en verde pero "Párrafo 2" es negro, porque el script se ejecutó sincrónicamente con el análisis HTML, por lo que solo encontró el primer párrafo, no el segundo.

En contraste, aquí hay un type="module"guión:

.found {
    color: green;
}
<p>Paragraph 1</p>
<script type="module">
    document.querySelectorAll("p").forEach(p => {
        p.classList.add("found");
    });
</script>
<p>Paragraph 2</p>

Observe cómo ambos están verdes ahora; el código no se ejecutó hasta que se completó el análisis de HTML. Eso también sería cierto con undefer script contenido externo (pero no contenido en línea).

(No era necesario realizar la NodeListcomprobación allí porque cualquier módulo de soporte de navegador moderno ya tieneforEach activado NodeList).

En este mundo moderno, no hay valor real para el DOMContentLoadedevento de la función "listo" que PrototypeJS, jQuery, ExtJS, Dojo y la mayoría de los demás proporcionaron en el pasado (y aún brindan); simplemente use módulos o defer. Incluso en el pasado, no había muchas razones para usarlos (y a menudo se usaban incorrectamente, reteniendo la presentación de la página mientras se cargaba toda la biblioteca jQuery porque scriptestaba en headel documento en lugar de después), algo que algunos desarrolladores en Google marcó desde el principio. Esto también fue parte del motivo de la recomendación de YUI de poner guiones al final del body, nuevamente en el pasado.

TJ Crowder
fuente
1
@FeRD - Gracias por editar. Los críticos lo rechazaron, pero estabas en lo cierto. :-)
TJ Crowder
O ponlo todo en una función y ponlo <body onload="doIt()>".
Justin Liu
@JustinLiu: el loadevento ocurre muy tarde en el ciclo de carga de la página, casi nunca es una buena práctica esperar para ejecutar JavaScript hasta entonces. Pero sí, es una opción. Por cierto, revisé la respuesta para 2020.
TJ Crowder
O podrías usardocument.addEventListener('DOMContentLoaded', function(){ //doyour stuff })
Justin Liu
@JustinLiu - Sí, mira el último párrafo. :-) Nunca he visto la necesidad de hacerlo, pero tú puedes.
TJ Crowder
6

Puede ejecutar código JavaScript en cualquier momento. AFAIK, se ejecuta en el momento en que el navegador llega a la etiqueta <script> donde se encuentra. Pero no se puede acceder a los elementos que aún no están cargados.

Entonces, si necesita acceder a los elementos, debe esperar hasta que se cargue el DOM (esto no significa que se cargue toda la página, incluidas las imágenes y demás. Es solo la estructura del documento, que se carga mucho antes, por lo que generalmente gana no noto un retraso), usando el DOMContentLoadedevento o funciones como $.readyen jQuery.

Mariana
fuente
2
El evento DOMContentLoaded se activará tan pronto como la jerarquía DOM se haya construido por completo, el evento de carga lo hará cuando todas las imágenes y subcuadros hayan terminado de cargarse.
BeauCielBleu