2011/03/08

Optimización de javascript (III). Alto rendimiento de UI con Javascript

Aunque a algunos no les guste oír ésto, soy de los que consideran que javascript es un lenguaje mucho más práctico a nivel general que java y por supuesto mucho más abarcable. Sí, java tiene clases para todo, pero no es práctico desde el momento que el rendimiento de lo que hagas se desploma por culpa de la máquina virtual, con la honrosa excepción de Android siempre con el prisma de que SOLO ejecuta java... si ejecutara otra cosa a lo mejor nos sorprenderían dispositivos que tienen una potencia que ya hubieran querido ordenadores de sobremesa de hace 3 o 4 años, pero bueno, eso es otra guerra.

La cuestión es que prácticamente nada es imposible con el tándem navejador-javascript, pero de nada sirve tener un ferrari si le echamos alcohol destilado de remolachas para que corra. Llega el momento de hacer buen código y que éste sea rápido. Vamos a ver cómo hacerlo desde el punto de vista de interfaces responsivas y ágiles.

Como dije en la segunda entrega, para la carga de una página la principal consideración a tener en cuenta es que el proceso de la UI del navegador es la responsable tanto de realizar los cambios en la interfaz de la página como de la ejecución de los javascripts y que mientras no se termine con la carga de la página no se procede a la carga de otros elementos como por ejemplo, las imágenes.

la regla general es que todos los cambios en la UI deben usar la información más actualizada o final para que sean aplicados.

100ms es el límite de usuario para percibir una aplicación como ágil.

Mejora de rendimiento de la UI
Mejoras en la carga de los javascripts
La carga de javascripts bloquea el refresco de la interfaz en la carga de páginas, de modo que en el momento que se llega a un tag de <script>, el proceso es:
refresco ui -> petición .js -> carga .js -> parse del .js -> ejecución del .js -> continua la carga de la página
De esta manera las primeras optimizaciones sencillas son
  • Colocar los scripts al final de las páginas
  • Reducir el número de scripts cargados en una página
Carga dinámica de scripts
La carga dinámica de scripts no es bloqueante, de modo que si se lanza la carga de forma dinámica, la interfaz de usuario sigue actualizándose mientras se hace la carga y el parse del .js en segundo plano.
La ejecución del script comienza inmediatamente después de terminar la carga y el parse del .js por lo que la temporización de la ejecución no está garantizada.

La técnica básica es:
function loadScript(url, callback) {
    var script  = document.createElement("script"), body = document.body;
    script.type = "text/javascript";
    if(callback) {
        // Se llama al callback cuando se termine la carga del script.
        if(script.readyState) { // IE <= 8
            script.onreadystatechange = function () {
                if(script.readyState == "loaded" ||
                   script.readystate == "complete") {
                    script.onreadystatechange = null;
                    callback();
                }
            };
        } else { // Otros
            script.onload = function () { callback(); };
        }
    }
    script.url = url;
    body.insertBefore(script, body.irstChild);
Uso:
loadScript(“foo.js”, function () { alert(“Cargado!”); } );
Técnica de scripts diferidos (defer)
Esta técnica está soportada por todos los navegadores menos Opera(?) desde versiones muy antiguas. Se usa la palabra clave defer dentro del tag script.
<script defer src=”foo.js”></script>
Los scripts diferidos se descargan inmediatamente y se parsean en segundo plano, pero no se ejecutan hasta que no se ha finalizado con la actualización de la UI.
Aunque la ejecución se retrasa hasta el final, no se garantiza que los scripts se ejecuten en el mismo orden en distintos navegadores.

Técnica de scripts asíncronos (async)
Esta técnica está soportada por todos los navegadores menos Internet Explorer(?) y Opera(?) desde versiones muy antiguas. Se usa la palabra clave async dentro del tag script.
<script async src=”foo.js”></script>
Los scripts asíncronos tienen un funcionamiento muy similar al de los scripts dinámicos ya que se descargan y parsean en segundo plano, pasando a ejecutarse en primer plano (bloqueando el refresco de la UI por lo tanto) inmediatamente según se termine su parseo.

Como consecuencia, el orden de ejecución de los scripts no tiene por qué mantenerse para scripts asíncronos.

Técnica de javascript no bloqueante

Un método de añadir código que bloquee la ejecución del ui-thread, es crear un repositorio de funciones para luego ir añadiendo funciones a medida que se va realizando la carga, para finalmente ejecutar las que hagan falta.

En el head, habría que incluir un script que define un objeto con un array de funciones (nuestro repositorio) en él:
<script>    var myapp = { funciones: []; }; // Crea el objeto myapp</script>
A medida que se va cargando la página, en vez de incluir código del tipo:
<script>alert("boo!");</script>
lo que se hace es
<script>
    myapp.funciones.push(function () {
        alert("boo!");
    });
</script>
Para ejecutar todas las funciones que se han añadido al repositorio habría que hacer:
var l = myapp.funciones.length;
for(var i = 0, i < l; i++) {
    myapp.funciones[i]();
}

No hay comentarios:

Publicar un comentario