Cuando cargas scripts de JavaScript en un documento HTML necesitas tener cuidado con el tiempo de carga de la página, ya que dependiendo del lugar donde cargues los scripts o del método de carga utilizado podrías generar un gran impacto en el rendimiento de la misma.
Lo más habitual es incluir los scripts de este modo:
<script src="script.js"></script>
Cuando el navegador analice la página y llegue a la línea en la que se define el script, éste se cargará mediante una petición adicional al servidor, ejecutándose posteriormente el script. Cuando este proceso finalice, el navegador continuará analizando el resto de los elementos de la página.
Si algún script definido en la cabecera de la página resulta muy pesado y tarda bastante en cargarse o si tarda demasiado en ejecutase, los visitantes verán una página en blanco hasta que el script se cargue por completo y se termine de ejecutar. Esto no es algo que alguien desee, especialmente cuando un visitante accede a la página desde un dispositivo móvil con una conexión de red lenta.
Contenidos
En dónde colocar los scripts
Vamos a ver primero los diferentes lugares en los que puedes colocar los scripts en un documento HTML.
Script en la cabecera
Cuando aprendes HTML resulta todavía muy habitual que te digan que los scripts se incluyen en la cabecera del documento, entre las etiquetas <head>
y </head>
, tal y como vemos en este ejemplo:
<html>
<head>
<title>Título</title>
<script src="script.js"></script>
</head>
<body>
...
</body>
</html>
Sin embargo, cuando el navegador analice la página y se encuentre con el script que hemos definido, lo cargará y lo ejecutará. Una vez haya realizado esta tarea, continuará analizando el body de la página. Sin embargo, esto conlleva una serie de problemas. Por un lado se introduce un retraso innecesario en la carga de la página y por otro, puede que ocurra algún error si el script se ejecuta y hace referencia a algún elemento de la página que todavía no se ha cargado.
A continuación puedes ver una representación del proceso de carga y del rendimiento de un script en el head de una página:
Tal y como puedes ver, el análisis del código HTML se pausa hasta que el script se carga y se ejecuta. Una vez se haya cargado y ejecutado, el análisis continuará. A continuación vamos a ver cómo solucionar estos problemas.
Script en el body
Las solución a los problemas anteriores consiste en incluir el script justo antes de la etiqueta de cierre </body>
. De este modo, el script se cargará y se ejecutará una vez la se haya analizado el código HTML, lo que conlleva una gran mejora en el rendimiento de la página:
<html>
<head>
<title>Título</title>
</head>
<body>
...
<script src="script.js"></script>
</body>
</html>
Este método es el mejor que puedes usar si es que necesitas dar soporte a navegadores antiguos que todavía no soportan los atributos async
y defer
. Si quieres, también puedes ver en detalle el siguiente artículo, en el que se explica con más detalle en dónde debes cargar los scripts.
A continuación puedes ver una representación del proceso de carga y del rendimiento de un script en el body de una página, justo antes del cierre de la etiqueta </body>
:
Tal y como puedes comprobar, no existen pausas desde que se inicia el análisis y el renderizado del código HTML hasta el que finaliza. Los scripts se cargan y se ejecutan una vez finaliza el renderizado. De este modo podemos mostrar la página al usuario antes de que los scripts se pidan al servidor.
Sin embargo, aunque el método que hemos visto soluciona muchos problemas, existen dos métodos más modernos mediante los cuales podemos cargar los scripts en la cabecera sin los problemas mencionados.
Carga scripts con async y defer
En los navegadores modernos, la etiqueta script
acepta los atributos booleanos async
y defer
. Estos atributos indican el método de carga del script. En caso de especificar ambos atributos, el atributo async
tiene precedencia sobre defer
, salvo que el navegador soporte el atributo defer
pero todavía no soporte async
, en cuyo caso se seleccionará defer
.
Script el head con async
El atributo async
solamente tiene sentido cuando cargamos el script en el head
de la página. Para cargar un script mediante este método basta con agregar el atributo en la etiqueta del script:
<html>
<head>
<title>Título</title>
<script async src="script.js"></script>
</head>
<body>
...
</body>
</html>
Cuando usas async
el script se carga de forma asíncrona al mismo tiempo que se analiza el el código HTML de la página. Sin embargo, cuando el script está listo, el análisis se pausa para ejecutar el script. Una vez termine la ejecución del script, el análisis de código HTML continúa hasta su finalización.
A continuación puedes ver una representación del proceso de carga y del rendimiento de un script que usa el atributo async
en el head de una página:
Mediante este método tenemos la ventaja de la carga en paralelo del script, pero sigue habiendo esperas.
Si usas el atributo async
cuando cargas el script en el body
, será inútil.
Script en el head con defer
Al igual que ocurre con el atributo async
, el atributo defer
solamente tiene sentido cuando cargamos el script en el head
de la página. Para cargar un script mediante este método simplemente debes agregar el atributo en la etiqueta del script:
<html>
<head>
<title>Título</title>
<script defer src="script.js"></script>
</head>
<body>
...
</body>
</html>
Cuando usas defer
el script se carga de forma asíncrona a la vez que se analiza el código HTML de la página. Sin embargo, el script no se ejecutará hasta que finalice el análisis del código HTML de la página, tal y como ocurría cuando colocábamos el script antes del cierre de la etiqueta body
. En esta ocasión tenemos la ventaja de que el script ya se habrá cargado, ganando un valioso tiempo.
A continuación puedes ver una representación del proceso de carga y del rendimiento de un script que usa el atributo defer
en el head de una página:
Con este método tenemos la ventaja de colocar el script en el body y la ventaja de que el script se cargue en paralelo a la vez que se analiza el código HTML. En términos de velocidad, este es sin duda el mejor método.
Si usas el atributo defer
será inútil cuando cargas el script en el body
.
Comparativa entre async y defer
Vamos a ver qué metodo utilizar en base a diferentes parámetros que también utiliza la utilidad PageSpeed Insights de Google.
Bloqueo del análisis HTML
Lo primero que hace el navegador es analizar el código HTML de la página. Cuando usamos async
se bloquea el análisis del código de la página, mientras que cuando usamos defer
el análisis del código HTML no se pausa hasta que finaliza.
Bloqueo del renderizado HTML
Si bloqueamos el análisis del código HTML, también estamos bloqueando indirectamente el renderizado del mismo, ya que el renderizado se realiza tras el análisis. Si embargo, aún cuando no haya pausas en el análisis del código HTML, podría bloquearse el renderizado del mismo dependiendo de la programación de los archivos JavaScript.
Aunque es un primero paso, ni el uso de async
ni el uso de defer
garantizan que no se vaya a bloquear el renderizado. Para evitar el bloqueo del renderizado tendrás que asegurarte de que los scripts se ejecuten tras el evento onLoad
.
Evento domInteractive
Aunque haya finalizado el renderizado del código HTML, la página todavía no será interactiva para el usuario. El evento domInteractive será el que indique el inicio de dicha interacción, siendo además una de las métricas de rendimiento de Google, por lo que es algo muy importante.
Los scripts cargados mediante defer
se ejecutarán justo después de que el navegador emita el evento domInteractive
, que ocurrirá una vez el DOM se haya inicializado, para lo cual, el código HTML se tendrá que haber cargado y ejecutado.
Los archivos CSS y las imágenes todavía no se habrán analizado ni cargado llegados a este punto. Una vez estén cargados, el navegador emitirá el evento domComplete, y luego el evento onLoad.
Orden de ejecución
Cuando cargamos scripts usando defer
, los scripts se ejecutarán uno tras otro, en el orden que los hemos definido, mientras que cuando los cargamos mediante async
, se cargarán nada más estar disponibles, por lo que el orden de carga no se garantiza.
Si cargas scripts con async
, podría darse el caso de que un script que dependa de otro se ejecute antes de que el otro se haya ejecutado, pudiendo ocasionar errores.
Compatibilidad
A día de hoy, los atributos async
y defer
tiene una alta compatibilidad, salvo que te vayas a versiones de navegadores de hace diez años que apenas son ya utilizadas.
A continuación puedes ver la tabla de compatibilidad de navegadores del atributo async
:
A continuación puedes ver la tabla de compatibilidad de navegadores del atributo defer
:
Qué método utilizar
Definitivamente, si quieres ganar rendimiento, el mejor lugar en el que puedes colocar los scripts es en el head
de la página siempre y cuando uses el atributo defer
:
<script defer src="script.js"></script>
Usando defer
nos aseguramos de que el código HTML se haya analizado cuando se ejecute el script y de que sus dependencias se hayan ejecutado previamente. Además, el evento domInteractive
se iniciará antes, haciendo felices a tus visitantes y arañando unos puntos en el posicionamiento de tu página.
El uso de async
no está mal, pero defer
gana por goleada.
Me ha resultado súper útil tío, muchas gracias.