Aunque una cuestión como esta (¿dónde coloco una etiqueta en una página?) puede parecer carente de sentido a simple vista, no lo es si pensamos en la enorme cantidad de scripts que se cargan hoy en día en algunas aplicaciones, y en especial en las aplicaciones de tipo Single Page Application (SPA).
Por ejemplo, observa los scripts que carga inicialmente una SPA como es GMail y que además está superoptimizada por parte de Google:
Pulsa para aumentar
Como vemos son en total 31 scripts que pesan casi 2MB entre todos, y tardan un tiempo considerable en cargar. Y eso sin contar los scripts que van embebidos en la propia página y que por lo tanto no se cargan de forma externa.
Así pues, ante tanto script y con semejante tamaño: ¿En qué parte de una página es mejor colocar los scripts?
El procesamiento de los elementos de una página
Si analizamos cómo carga una página sus contenidos podemos ver enseguida que el lugar donde se coloquen puede tener mucha influencia sobre su rendimiento.
Una vez que el navegador se descarga el código fuente de una página web comienza a analizar sus etiquetas por orden, de arriba a abajo. Es decir, en una página normal primero leerá el doctype, luego la etiqueta raíz HTML, dentro de ésta la cabecera, los elementos de ésta, luego el cuerpo, etc...
En el caso concreto de los scripts, éstos deben ejecutarse a medida que se encuentran en la página. Por ello si colocamos una etiqueta <script> apuntando a un archivo .js externo, antes de continuar cargando el resto de la página y seguir montando el DOM, el navegador debe descargar ese archivo e interpretar y ejecutar su código.
Por este motivo, los scripts que están en la cabecera deben descargarse del servidor y deben ser interpretados antes de hacer nada más. Es evidente que esto, con scripts grandes y complejos, ralentiza la carga de la página.
Sin embargo el lugar tradicional para ubicar los scripts siempre ha sido la cabecera. Es más, incluso se consideraba una buena práctica porque así estaban todos en el mismo sitio y era todo más ordenado. Claro que eran otros tiempos, y los scripts de la época eran por regla general sencillos y poco pesados.
Nota: Que quede claro que aunque el funcionamiento es como el que describo, en una página normal y corriente el efecto de poner los scripts en un lugar o en otro es totalmente indetectable y no se notará disminución de velocidad ni merma del rendimiento. Yo mismo, de hecho, suelo ponerlos siempre en la cabecera por costumbre. Es solamente en páginas con scripts grandes y complejos donde este efecto puede llegar a notarse.
Por todo esto la recomendación en aplicaciones complejas es que pongamos las etiquetas de script lo más tarde posible en la página, justo antes de la etiqueta de cierre del cuerpo (</body>), de modo que todo el HTML haya ya sido procesado, el DOM esté creado y el retardo debido a cargar e interpretar los scripts no influya en el tiempo de carga percibido por el usuario.
Nota: Otra cuestión importante a tener en cuenta es que en la mayor parte de las aplicaciones SPA grandes los scripts se crean en forma de módulos y se cargan dinámicamente usando alguna biblioteca que implemente la API Asynchronous Module Definition o AMD, la más común y utilizada de la cuales es Require,js. Esto presenta entre otras muchas la ventaja de que solo se cargan los módulos cuando se necesitan y no antes (acelerando la carga global de la página) y que además definimos dependencias entre ellos, el orden adecuado para cargar, la carga asíncrona de los mismos, etc... Quizá haga algún artículo sobre el tema un día de estos...
El atributo async
Otra opción muy interesante que nos habilita HTML5 es especificar explícitamente que la carga de nuestros scripts debe hacerse de manera asíncrona.
En realidad, si los scripts que debemos cargar no ejecutan código, sino que se encargan simplemente de definir clases y módulos que luego vamos a utilizar al terminar de cargar la página, obligar a que éstos se procesen e interpreten durante la carga no es necesario.
Para conseguirlo lo único que tenemos que hacer es marcar la etiqueta script con el atributo async, así:
<script type="text/javascript" src="scripts/miScript.js" async></script>
De este modo cuando el intérprete del navegador se encuentra esta etiqueta en una página lo que hace es empezar la descagra e interpretación del script en segundo plano, mientras sigue con el procesamiento del resto del código fuente de la página. Así no se detiene la carga de la página como pasaría normalmente.
El soporte de esta etiqueta es casi universal como podemos ver en CanIUse:
y solo los navegadores antiguos (concretamente IE9 o anterior) cargarían los scripts del modo normal.
Dos cosas importantes a tener en cuenta con este atributo:
- Solo funciona en scripts externos, es decir, scripts que tengan el atributo src para apuntar a un archivo .js. En scripts en línea evidentemente carece de sentido.
- Dado que los scritps se cargan en segundo plano puede darse la circunstancia de que archivos que están más adelante en el código pero son más pequeños se carguen e interpreten antes que archivos .js anteriores en el código. Si el orden de ejecución es importante no nos servirá, así que mucho ojo.
El atributo defer
Otro atributo muy interesante para los scritps es defer. Este atributo lo que consigue es diferir la carga e interpretación de los scripts marcados con el mismo hasta que la página se haya cargado por completo, independientemente de donde estén colocados en el código.
Su sintaxis es muy sencilla:
<script type="text/javascript" src="scripts/miScript.js" defer></script>
De este modo la etiqueta no se utiliza hasta que la página ha cargado por completo. Así el incluir los scripts aunque sea en la cabecera no ralentiza para nada la carga de la página.
El soporte es universal también:
En este caso el orden de ejecución sí que se conserva por lo que no tiene el problema que causaba el uso de async, y garantizamos un orden de ejecución.
De mismo modo este atributo solo tiene efecto en scripts externos, y no en scripts en línea.
OJO: Internet Explorer fue el "inventor" de este atributo allá por el año 1997 ¡con IE4!. En versiones antiguas de este navegador (hasta la 9, por eso están en un verde diferente en la figura anterior) el atributo defer sí que actuaba sobre los fragmentos en línea, lo cual cambia el comportamiento que tenemos actualmente. A mi me parecía más lógico, la verdad. Aunque no influye demasiado en el código, salvo por el orden de ejecución, puede dar algunos problemas si no lo tenemos en cuenta. En su día, hace ya más de un lustro, escribí un post sobre este atributo cuando solo lo soportaban IE y Firefox, aunque este último lo hacía mal. En él se puede ver el efecto que tenía anteriormente.
Actualización Dic2016: Addy Osmani de Google dice que usarlos en V8 aumenta el rendimiento de carga en un 10%.
En resumen
En páginas y aplicaciones normales no tiene demasiada importancia dónde coloquemos las etiquetas de script dentro del código HTML. En aplicaciones grandes y en las que cada milésima de segundo cuenta para el rendimiento tenemos diversas opciones para cargar scripts de manera que no impacten en la velocidad de carga de la página:
- Colocarlos al final de todo del cuerpo, antes de la etiqueta </body>.
- Usar la API de carga asíncrona de módulos, AMD.
- Utilizar el atributo async.
- Usar el atributo defer.
¡Espero que te resulte útil!