2011/03/09

Optimización de javascript (IV). Técnicas de agilización de UI en tiempo de ejecución

La ejecución de funciones en javascript bloquean el refresco de la interfaz, de modo que mientras se están ejecutando no se actualiza la interfaz. Si un script se ejecuta durante demasiado tiempo, el navegador dará indicaciones de que no está respondiendo. El objetivo es mantener todas las ejecuciones de funciones javascript por debajo de los 50ms.

Para evitar que un proceso pesado bloquee la UI, están las siguientes técnicas. La función de referencia es la siguiente
function processArray(items, process, callback) {
        for (var i, len = items.length; i < len; i++) {
                process(items[i]);
        }
        callback();
}
Técnica de timers
Los timers permiten la ejecución de una función, añadiéndola a la cola de la UI para que se lance cuando hayan pasado x milisegundos.
setTimeout( function() {  ...código... }, 500); // Ejecuta la función después de 500ms
La función anterior quedaría como
function timedProcessArray(items, process, callback) {
        var todo = items.concat(); // Crea un clon de la lista de elementos original
        setTimeout(function() {
                var start = +new Date();
                do {
                        process(todo.shift());
                } while(todo.length > 0 && (+new Date() - start < 50));
                if(todo.length > 0) {
                        setTimeout(arguments.callee, 25)
                } else {
                        callback(items);
                }
        }, 25);
}
Técnica de web workers
Los web workers son scripts .js que se ejecutan de forma asíncrona en segundo plano respecto a la página que los invoca. Por lo tanto, al no ejecutarse en el proceso de la UI, no bloquean la actualización de la UI.
Están soportados por todos los navegadores menos el Internet explorer desde versiones muy antiguas.
Tienen una API serializada por datos, que se serializan cuando se les pasa al worker o son devueltos por él.
Los workers NO tienen acceso ni al DOM ni al BOM ya que se ejecutan en un entorno completamente separado.
// En la página principal
var worker = new Worker(“proceso.js”);
worker.onmessage = function(event) { useData(event.data); };
worker.postMessage(valores);
// En el worker (proceso.js)
self.onmessage = function(event) {
        var items = event.data;
        for(var i = 0, len = items.length; i < len; i++) {
                process(items[i]);
        }
        self.postMessage(items);
}

2011/03/08

dhteumeuleu

dhteumeuleu

Este sitio es simplemente im-pre-sio-nan-te. Es increible el rendimiento que tiene (en chrome) y más teniendo en cuenta que no había visto nada parecido casi ni con flash... y lo más, las demos de 2006 y antes siguen siendo un prodigio técnico incluso ahora.

No usa ni HTML5, ni CSS3, ni jQuery, ni leches... javascript puro y duro con algo de DOM para manipulación y SVG en algunas demos.

Y lo más grande, es que las técnicas que usa para la mayoría son de lo más sencillas...

una clase para encapsular el código y reducir el ámbito, un timeout cada 32ms, una funcion de easing, algunas transformaciones matemáticas con una pizca de trigonometría y mucho, pero mucho arte.

100% recomendable.

Otra demostración de que flash es una tecnología a extinguir

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]();
}

2011/03/07

Todo es cuestión de referencia

A la hora de enfrentarse con la hoja en blanco, siempre hay que tener a mano una buena referencia de referencia y más cuando se tienen que manejar a la vez tantas tecnologías como pasa en el desarrollo web.

Por ello, surgen páginas como GotApi que centralizan la referencia de varios lenguajes y tecnologías en un único sitio.

Las principales ventajas de esta web frente a otras son que
  1. Bebe directamente de las fuentes. Abre las páginas de documentación oficiales
  2. Un único anillo para atarlos a todos (y traerlos a las tinieblas). En una pestaña puedes tener todas las referencias. Rápido y sencillo, como debe ser.