La metodología clásica de creación de páginas Web recomienda que todos nuestros scripts se encuentren en la cabecera de la página. Está bien tener un orden pero realmente no es necesario. Es más, nadie nos dice que no podamos incluso tener varias cabeceras en la página, una por ejemplo, después de cerrar la etiqueta </body>, si queremos.

Hay algunas aplicaciones que usan mucho JavaScript. Sobre todo ahora con el uso masivo de capacidades AJAX en las aplicaciones Web. Algunos de estos scripts son simples bibliotecas de funciones con decenas o cientos de ellas que lo único que hacen es retrasar la carga de página mientras son procesadas por el navegador, ya que éste no sólo carga cada Script sino que también lo interpreta y ejecuta a medida que lo va leyendo. En otros casos el script necesita tener acceso a todos los elementos de la página antes de poder actuar (es decir, el HTML de la página debe ser procesado por completo y el árbol DOM construido). Si lo colocamos en la cabecera no funcionará lo cual nos fuerza a ponerlo después del cierre del cuerpo, lo cual, por otra parte no es un problema, pero debemos recordárselo a los que usen la biblioteca de funciones. Mi biblioteca AJAXInterceptor es un buen ejemplo de esto, ya que requiere que se coloque al final para funcionar.

Para evitar estos problemas y, sobre todo, acelerar la carga de las páginas, Microsoft introdujo en Internet Explorer 4.0 hace ya unos años el atributo defer de JavaScript. Posteriormente este atributo ha pasado a formar parte de la especificación HTML 4.01 y posteriores.

Básicamente lo que consigue es que el navegador no procese el código JavaScript hasta que la página ha cargado por completo, al final del todo, en lugar de en el momento de leerlo. Está pensado para que si tu Script no contiene ninguna sentencia document.write o similares que modifiquen el contenido del DOM, entonces que se pueda procesar todo esto al final, acelerando la carga de la página.
Su sintaxis es la siguiente:

    <script type="text/javascript" defer="defer">

El efecto real es como si tu Script, esté en donde esté colocado en la página, estuviese colocado justo al final de la misma, puesto que no se procesará hasta que el resto de la página se haya procesado.

OJO: que se procese al final no quiere decir que no se descargue cuando toca. Al encontrarse una etiqueta <script> que hace referencia a un archivo externo, el navegador descargará igualmente el script, sólo que no lo procesa. Así que no nos libramos de esta fase, aunque normalmente sólo lo hará la primera vez si tenemos una adecuada política de caché. Para retrasar también la descarga, en la nueva especificación de HTML se está trabajando en un nuevo atributo async que servirá precisamente para eso y que a mí me parece muy interesante.

He preparado un ejemplo que muestra cómo funciona este atributo en una página:

Lo que se hace es introducir una mezcla de scripts tanto in-line como con referencia externas, algunos de los cuales están en la cabecera y otros en el cuerpo y algunos tienen defer y otros no. Cada uno de ellos simplemente anota en una variable un texto indicando qué tipo es. Al final hay un botón que muestra la cadena en el orden en el que se concatenó, lo que nos da el orden de ejecución del Script.

Puedes descargar el ejemplo aquí.

En Internet Explorer 8.0 (en realidad en cualqueir versión de IE desde la 4.0) el resultado es el siguiente:

Que es lo que cabría esperar, y como vemos los scripts con defer se ejecutan al final, en el mismo orden en el que están en la página.

Sin embargo si lo ejecutamos en Firefox 3.0.1 obtenemos esto:

Es decir, el comportamiento normal, como si no tuviera defer aplicado. ¿Qué ha pasado?

Lo que ocurre es que Firefox no soporta el estándar y hace caso omiso de este atributo, por lo que la ejecución es exactamente la misma que en el caso de no tener defer :-(

Este es un bug reconocido por Mozilla ¡desde el año 2000 nada menos!. Lo cierto es que en la inminente versión 3.5 del navegador, por fin, lo han solucionado y ahora funcionará bien. Pero el caso es que no nos servirá por el momento.

Los otros navegadores (opera, Safari, Chrome...) ni siquiera tienen intención de soportarla. No deja de llamarme la atención cómo para algunas cosas dicen ser tan cumplidores de los estándares y con otras pasan absolutamente de todo :-(

Mi recomendación

Para mí el atributo defer es interesantísimo, así como el futuro posible atributo async, pero el caso es que hoy por hoy sólo deberíamos utilizarlo cuando estemos seguros de que nuestra página la verá únicamente Internet Explorer.

Si queremos una solución que funcione para todos los navegadores, lo mejor es colocar los scripts al final de la página, justo después del body, para asegurarnos de que al menos el grueso de la página se visualiza y luego los scripts pesados se procesan sin interferir en esta primera visualización.

Otra opción es cargar dinámicamente los scripts escribiendo código similar al que os comentaba el otro día para mantener la sesión abierta.

Nota final: Detectar si se hay scripts deferidos o no en una página

Cómo puedo detectar si defer funciona o no en una página.

Muy sencillo, con un código como este:

    <script type="text/javascript">
        var _deferFunciona = true;
    </script>
    <script type="text/javascript" defer="defer">
        _deferFunciona = false;
    </script>
    <script type="text/javascript">
        var deferFunciona = false;
        if (_deferFunciona)
          deferFunciona = true;
    </script>

Este ejemplo está también en el ZIP que te puedes descargar.

En resumen

Es una verdadera lástima que este atributo tan útil no funcione en todos los navegadores, sobre todo considerando que hace años que está en el estándar :-(

Si vas a hacer una página específica para Internet Explorer no dudes en usarlo. Si es para otro navegador usa otras técnicas para asegurarte de que los scripts no interfieren y ralentizan la carga de páginas pesadas.

💪🏻 ¿Este post te ha ayudado?, ¿has aprendido algo nuevo?
Pues NO te pido que me invites a un café... Te pido algo más fácil y mucho mejor

Escrito por un humano, no por una IA