Contenido

Optimizando el rendimiento de nuestros scritps jQuery

13 May

+ 23

Que soy un fan de jQuery no es una novedad, pero si que es una novedad el que no lo uso de la forma más correcta. Por lo menos así lo ven la gente de ArtzStudio, que nos muestran una serie de indicaciones con el fin de optimizar el uso del CPU el usuario mejorando el rendimiento de nuestros scripts.

1) Siempre desciende desde un #ID

Siempre el selector más rápido y que menos iteraciones produce es la búsqueda por un #id, ya que simplemente lanza un document.getElementById(), lo que al ser nativo nos asegura un tiempo de respuesta mínimo y un mínimo consumo de proceso.

<div id="content">
	<form method="post" action="/">
		<h2>Traffic Light</h2>
		<ul id="traffic_light">
			<li><input type="radio" class="on" name="light" value="red" /> Red</li>
			<li><input type="radio" class="off" name="light" value="yellow" /> Yellow</li>
			<li><input type="radio" class="off" name="light" value="green" /> Green</li>
		</ul>
		<input class="button" id="traffic_button" type="submit" value="Go" />
	</form>
</div>

Con el código anterior, si queremos coger el elemento #traffic_button, siempre nos será más rápido hacerlo directamente que medainte la class del mismo elemento.

// Más lento
var traffic_button = $('#content .button');

// Más rápido
var traffic_button = $('#traffic_button');

Seleccionar varios elementos

Si por contra, necesitamos seleccionar una serie de elementos, siempre que podamos debemos hacerlo partiendo de un #id, de esta forma nos aseguramos una iteración más ajustada.

var traffic_lights = $('#traffic_light input');

Aclaración

Hay que destacar que esto afectará positivamente al rendimiento, aunque el uso de selectores más complejos permiten mejorar la flexibilidad de nuestros scripts. Es importante tener en cuenta esta recomendación, pero no se puede aplicar a todos los casos.

2) Usa tags delante de las clases

Al contrario que con el #id, en el que se innecesario indicar el tag asociado a ese #id (div#page), esto no tiene sentido y además penaliza a lo que comentamos en el punto 1. En la selección de clases, es recomendable indicar el tag del elemento solicitado ya que al hacer uso de document.getElementsByTagName() para localizarlos acotamos previamente los elementos en los que buscar las clases solicitadas. Con el mismo código:

<div id="content">
	<form method="post" action="/">
		<h2>Traffic Light</h2>
		<ul id="traffic_light">
			<li><input type="radio" class="on" name="light" value="red" /> Red</li>
			<li><input type="radio" class="off" name="light" value="yellow" /> Yellow</li>
			<li><input type="radio" class="off" name="light" value="green" /> Green</li>
		</ul>
		<input class="button" id="traffic_button" type="submit" value="Go" />
	</form>
</div>

Si queremos seleccionar el elemento radio con class on, la opción rápida sería la siguiente:

var active_light = $('#traffic_light input.on');

3) Usa la caché de objetos

Coger el hábito de guardar una variable con el valor de un objeto jQuery nos evita realizar una serie de comprobaciones innecesarias y que en scripts pesados pueden suponer un aumento de rendimiento preocupante.

// NO
$('#traffic_light input.on).bind('click', function(){...});
$('#traffic_light input.on).css('border', '3px dashed yellow');
$('#traffic_light input.on).css('background-color', 'orange');
$('#traffic_light input.on).fadeIn('slow');

// SI
var $active_light = $('#traffic_light input.on');
$active_light.bind('click', function(){...});
$active_light.css('border', '3px dashed yellow');
$active_light.css('background-color', 'orange');
$active_light.fadeIn('slow');

4) Aprovechate del encadenamiento

El encadenamiento pese a producir un amasijo de código que no contribuye a mejorar la lectura del código hace que nuestro Javascript sea más ligero y puede ayudar a mejorar el rendimiento de nuestros scritps. Veamos el ejemplo anterior:

var $active_light = $('#traffic_light input.on');
$active_light.bind('click', function(){...})
	.css('border', '3px dashed yellow')
	.css('background-color', 'orange')
	.fadeIn('slow');

5) Usa subqueries siempre que puedas.

Si debemos realizar varias búsquedas sobre un elemento es altamente recomendable realizar un uso intensivo del método find() de jQuery para realizar las búsquedas. Mejoraremos el tiempo de respuesta al evitarnos búsquedas previas ya realizadas.

Usando el código HTML anterior podemos hacer lo siguiente:

var $traffic_light = $('#traffic_light'),
	$active_light = $traffic_light.find('input.on'),
	$inactive_lights = $traffic_light.find('input.off');

6) Limita la manipulación de DOM

Ya vimos hace un tiempo que la manipulación directa del arbol DOM es muy costoso y lento. Por eso el limitar el uso de esta técnica nos favorecerá a la hora de procesar nuestros scripts.

// Más lento
var top_100_list = [...], // assume this has 100 unique strings
	$mylist = $('#mylist'); // jQuery selects our <ul> element

for (var i=0, l=top_100_list.length; i<l; i++) {
	$mylist.append('<li>' + top_100_list[i] + '</li>');
}

// Más rápido
var top_100_list = [...], // assume this has 100 unique strings
	$mylist = $('#mylist'), // jQuery selects our <ul> element
	top_100_li = ""; // This will store our list items

for (var i=0, l=top_100_list.length; i<l; i++){
	top_100_li += '<li>' + top_100_list[i] + '</li>';
}
$mylist.html(top_100_li);

7) Aprovechate de la delegación de eventos

Mientras no se diga lo contrario los eventos se propagan hasta el elemento padre. Esto tiene una utilidad realmente interesante y nos puede ayudar a evitar tener que realizar busquedas muy concretas.

// Menos eficiente
$('#entryform input).bind('focus', function(){
	$(this).addClass('selected');
}).bind('blur', function(){
	$(this).removeClass('selected');
});

// Más eficiente
$('#entryform).bind('focus', function(e){
	var cell = $(e.target);  // e.target almacena el nodo que ha lanzado el evento.
	cell.addClass('selected');
}).bind('blur', function(e){
	var cell = $(e.target);
	cell.removeClass('selected');
});

8 ) Elimina consultas innecesarias

Prácticamente todos los puntos anteriores, nos dan soluciones para realizar el mínimo de consultas posibles realizando las mismas funciones. Pero este punto ataca más directamente a llamadas innecesarias como por ejemplo el $(document).ready(); que podemos evitar.

// Menos eficiente
$(document).ready(function(){
	mylib.article.init();
});

// Más eficiente
<script type="text/javascript>
mylib.article.init();
</script>
</body>

Como vemos la clave está en lanzar el <script /> justo antes de cerrar el tag </body> lo que nos asegura que todos los elementos que podemos necesitar están ya cargados en el árbol DOM.

9) Piensa en usar $(window)load()

Aunque $(document).ready() es muy útil y potente, no espera a que se carguen todos los elementos (imagenes, flash,..) para ejecutarse. Lo que puede producir un problema ya que está cargando nuestros scripts y además el navegador está trabajando en mostrar por pantalla otros elementos.

En estos casos, quizas nos sería conveniente usar $(window).load().

$(window).load(function(){
...
});

Esta función, esperará a cargar toda la página (imagenes, flash,…) incluidos, para lanzar nuestros scripts.

10) Comprime tu código

Este punto es discutible ya que al comprimir tu código estás haciendo que el navegador del usuario lo haya de descomprimir, lo que puede incrementar el rendimiento requerido frente a la velocidad de carga del script. Es interesnate tener en cuenta los factores que intervienen para decidir que acción realizar.

11) Aprenderte la librería

A estas alturas seguro que ya la conoces, pero no está de más tener a mano alguna Cheat Sheet(.pdf) para evitar caer en soluciones poco optimizadas.

12) Usa html() con cabeza

Como nos recuenda Joseanpg en los comentarios, el uso de html() nos penaliza sustancialmente el rendimiento de nuestros scripts. jQuery, al contrario de MooTools y Prototype (no he mirado los demás) no injecta el código HTML mediante innerHTML, sinó que construye los elementos DOM y los añade al nodo indicado.

Este proceso es más lento que usar un simple innerHTML aunque nos puede provocar problemas en insercciones en las que necesitemos refrescar el arbol DOM.

En la mayoría de casos, la mejor opción es usar innerHTML en nuestros scripts ya que añaden elementos perfectamente a nuestro arbol DOM y es realmente rápido. Para todo lo demás, html().

  • Excelente artículo, solo un apunte:

    En los puntos 3,4 y 5 has declarado algunas var con el nombre comenzando por dollar ($). No se si funcionaria esto en javascript, pero bueno te lo aviso por si ha sido un despiste (o acto reflejo del php).

    Un saludo 😉

  • Un comentario sobre el Apartado 6: ya que estamos optimizando ¿y si pasamos de la interfaz de jquery en esa tarea tan frecuente de ‘dictarle’ el HTML al elemento? Es decir:


    $mylist.html(top_100_li);

    Pasaría al típico:


    $mylist[0].innerHTML = top_100_li;

    Si os empeñais en no usar el innerHTML podemos usar este otro enfoque un poco más rápido que el método html() (es lo que hace el método tras unas comprobaciones un tanto superfluas en este caso):


    $mylist.empty().append( top_100_li);

  • Hay una opción que extrañamente es poco utilizada y conocida, a pesar de formar parte de la función más popular de jQuery: es posible pasar un segundo argumento a la función jQuery() (o $()) en la forma $(expresión, contexto), de forma de limitar las consultas a una sección particular del documento

    Ejemplo (datos obtenidos con Firebug):

    
    // Seleccionar enlaces en los posts
    $('.entry-content a'); //12.528ms
    $('.entry-content a', '#main'); // 5.909ms
    
  • Yo soy el primero en desaconsejar opciones poco optiimizadas pero estamos hablando de milisegundos que en el peor de los casos con un portatil medio cutre con atom te suma medio segundo más XD

  • @jay: Si, pero ahí es donde se marca la diferencia. Si puedes hacer las cosas bien, sabiendo que tienes las opciones para hacerlas. ¿Por que no hacerlas?

  • @joseanpg: Completamente de acuerdo. Lo añado al post.

  • Solo un detalle al momento de encadenar en el punto #4 en que le asignación de propiedades por css también puede ser así:

    
    var $active_light = $('#traffic_light input.on').bind('click', function(){...})
    	.css({
                            'border': '3px dashed yellow',
    	        'background-color':'orange'
                    })
    	.fadeIn('slow');
    
    
  • @aNieto2k: Ahí tienes toda la razón pero tambien hay que pararse a pensar cuanto tarda en cargar una página y por ejemplo, tu web, 4,368 segundos medidos que es bastante bueno (no eres google pero no está nada mal :P).

    Supongamos que consigues optimizar 10 milisegundos por llamada (siendo realistas) en 30 llamadas al principio cuando carga la página (que dudo que llegue a 10).

    Lo que estas ganando son 0,3 segundos, si me digeses mejorar tiempo del servidor completamente de acuerdo, ahora, al usuario final 0,3 segundos pa abajo o pa arriba le dan igual…

  • @jay: Si claro, pero esa medición es desde tu máquina. Que probablemente está bastante bien, osea que tenga unos GB de RAM, que tu procesador es más o menos moderno,…

    Estas opmizaciones, pueden suponer segundos, dependiendo de los scripts y de las máquinas de los usuarios.

    Imagina un 486 (que los debe haber todavía) con 128mb de RAM y Windows 95. Esa mejora, seguro que les hace un favor 😀

    Y conociendo las herramientas, podemos aplicarlas. Para las mejores máquinas/conexiones la ventaja será de pocos milisegundos, y para las máquinas/conexiones más antíguas tambien tendrán una mejoría.

    Todos ganan 😀

  • Llevas razón (reconozco que me duele decirlo xD), nome había parado a pensar en máquinas tan antiguas…

  • El post está excelente, pero para los que no somos expertos con jQuery tanto código no nos dice mucho, que pena por ser tan exigente, pero algunos ejemplos no caerian nada mal :$.

  • @Angelfire: He intentado que los ejemplos fueran lo más claros posibles, si han quedado algunas dudas, podrías hacerlas aquí en los comentarios y encantados, yo o cualquier otro usuario, te responderemos. 😉

  • @jay: No tiene que doler, son diferentes puntos de vista. Ambos igual de válidos y a tener en cuenta. Por eso es bonito esto del desarrollo, por que dependiendo del proyecto, las especificaciones/necesidades son completamente diferentes y te obligan a hacer borrón y cuenta nueva en cada proyecto 😀

  • @aNieto2k: No no, lo que me duele es admitir que estaba equivocado xD.
    PD: Ya verás pa la proxima ya… 😛

  • en el ejemplo 6 al top_100_li falta declararlo , como poner var top_100_li …. no se eso me parecio que faltaba

  • ¿hay alguna forma de mejorar las animaciones para que no produzcan saltos bruscos?

    ¿tienen estos saltos algo que ver con la forma de iyectar html de jquery?

    Estas mejores realmente deben contenplarse (de hecho ya me las he grabado a fuego :P) no obstante luego solo mejoran milisegundos.

    Por otro lado luego cuando vamos a hacer una animación de una imagen relativamente grande esta produce saltos. Me gustaría saber como mejorar esto. Y quizá podría ser el punto 13 de este artículo.

  • @Blacknet: El injectar HTML de jQuery es de lo más lento que existe. Pueba con

    $("#elem").get(0).innerHTML = "TEXTO";

    Debería irte más rápido.

  • al utilizar la lib «jquery.validate.min.js» al momento de validar se tarda mucho y se tarda aun mas si tienes muchos campos que validar… coomo le harias para optmizarlo?… espero me puedas ayudar gracias

Comentar

#

Me reservo el derecho de eliminar y/o modificar los comentarios que contengan lenguaje inapropiado, spam u otras conductas no apropiadas en una comunidad civilizada. Si tu comentario no aparece, puede ser que akismet lo haya capturado, cada día lo reviso y lo coloco en su lugar. Siento las molestias.