En el blog de SproutCore publican un interesante artículo sobre como mejorar el tiempo de carga de nuestras páginas al usando eval()
. Es por todos conocidos las anomalías con las que nos encontramos al usar esta función del lenguaje, pero en algunos casos, puede ayudarnos a mejorar la carga de nuestras páginas.
Okito, nos comenta que la inspiración le llegó al revisar como Gmail gestionaba el Javascript de la página en su versión móvil, usándo un sistema de carga asíncrona que posteriormete es parseada con regexp y evaluada con eval()
.
En la versión 1.0 de SproutCore han implementado esta técnica y presume una mejora de 8 segundos en una aplicación de prueba sobre Internet Explorer.
El problema
El problema radica en que las aplicaciones muy ricas en Javascript suelen ser muy pesadas para el procesador y el navegador, despues de descargar el JS, parsear el script lo ha de evaluar (internamente) para poner a nuestra disposición todas las funcionalidades que hayamos desarrollado, en este último punto es donde más se penaliza la ejecución de JS (hasta un 80% del tiempo de carga total).
La propuesta
Para solventar el problema, nos propone cargar el Javascript de una forma diferente, en lugar de código usar comentarios. Osea, hacer que todo el código Javascript sea un gran comentario.
/*
function mifuncion(){
....
}
var lala = mifuncion();
lala.prototype.bla = function(){
...
}
....
*/
Explicación
Al tratarse de un comentario el navegador no lo evalua y nos ahorramos un tiempo de carga muy valioso y podemos hacer que la carga del código sea cosa nuestra haciendo uso únicamente de él cuando lo necesitemos. Esto nos permite tener un control sobre el Javascript que está ejecutado en todo momento.

(Ver Imagen)
Como vemos en el gráfico, la evaluación de comentarios es ligeramente menor a la de una cadena de texto y muy menor a la de funciones.
En dispositivos móviles este tiempo parece más significativo como podemos ver en la imagen siguiente:

(Ver Imagen)
Implementándolo
<html>
[...]
<!-- Javascript que será ejecutado posteriormente -->
<script id="lazy">
/*
Código Javascript
*/
</script>
[...]
<script>
// Función encargada de recoger el contenido del <script id="lazy" /> y evaluarlo
function lazyLoad() {
var lazyElement = document.getElementById('lazy');
var lazyElementBody = lazyElement.innerHTML;
var jsCode = lazyElementBody.replace(/(\/\*|\*\/)/g,''); // Eliminamos (/*|*/)
eval(jsCode);
}
</script>
[...]
<!-- Activamos la carga del JS al hacer click-->
<div onclick=lazyLoad()> Lazy Load </div>
</html>
Como vemos en el código, el sistema básicamente lo que hace es cargar el javascript como un tag con un el código Javascript comentado completamente por lo que el navegador lo omitirá en el momento de evaluarlo.
lazyLoad()
se encarga de coger el innerHTML
del elemento que hemos cargado previamente con el javascript comentado, elimina los carácteres que indica que sea un comentario (en el ejemplo elminará todos los carácteres /*
y */
del innerHTML, por que hay que usarlo con cabeza) y posteriormente lo evalua con eval()
.
Resultado
La ventaja de este sistema es que nos permite ejecutar ese código cuando queremos y no cuando está cargando la página, mayor control y sobretodo mayor velocidad global de la aplicación.
En los ejemplo de SproutCore obtienen una mejoría de entre 3x y 20x frente a la versión de carga secuencial de Javascript.
Optimizando, que es gerundio
Gerardo hace una puntualización en un comentario que me hace rectificar. Como en el 100% de los casos las expresiones regulares son más lentas que las funciones nativas de los objetos Javascript. En este caso, hacer un substring()
nos hace reducir el tiempo de carga considerablemente.
Primero he hecho una prueba con HTML y cargando un fichero JS pesado, he usado Ext.js (versión comprimida 130kb) y he hecho una carga limpia del fichero.

(Ver Imagen)
Vemos el tiempo de carga del fichero Javascript acercándose a los 4 segundos. Y despues he hecho una quitando la llamada al fichero externo y lo he añadido como comentario en el HTML.

(Ver Imagen)
El tiempo de carga se reduce considerablemente. Pero este tiempo es únicamente el que tarda el navegador en recibir el fichero no tiene en cuenta el tiempo de ejecución del mismo.
Por otro lado he hecho pruebas de uso entre replace()
y substring()
.
Código
// Versión usando replace
function conreplace(){
var el = document.getElementById("lazy");
var text = el.innerHTML;
jsCode = text.replace(/(\/\*|\*\/)/g,'');
eval(jsCode);
}
// Versión usando substring
function consubstring(){
var el = document.getElementById("lazy");
var text = el.innerHTML;
var ini = text.indexOf("/*") + 2;// 2 == "/*".length
var fin = text.lastIndexOf("*/");
jsCode = text.substring(ini , fin);
eval(jsCode);
}
Resultados
- Replace: 322ms.
- SubString: 159ms.
Obviamente el replace elimina más comentarios del código que el subString, pero para este caso no necesitamos complicarnos más. Dejo el ejemplo optimizando a continuación.
<html>
[...]
<!-- Javascript que será ejecutado posteriormente -->
<script id="lazy">
/*
Código Javascript
*/
</script>
[...]
<script>
// Función encargada de recoger el contenido del <script id="lazy" /> y evaluarlo
function lazyLoad() {
var lazyElement = document.getElementById('lazy');
var lazyElementBody = lazyElement.innerHTML;
var ini = lazyElementBody.indexOf("/*") + "/*".length;
var fin = lazyElementBody.lastIndexOf("*/");
jsCode = lazyElementBody.substring(ini , fin); // Eliminamos (/*|*/)
eval(jsCode);
}
</script>
[...]
<!-- Activamos la carga del JS al hacer click-->
<div onclick=lazyLoad()> Lazy Load </div>
</html>
Estos pequeños detalles son intersantes a tener en cuenta.