Buenas prácticas con jQuery

Algunos consejos prácticos que os pueden ser de utilidad después de leer mi tutorial rápido de jQuery. ¿Tienes cualquier otro consejo o sugerencia, o cualquier puntualización al respecto? Tu comentario será MUY bienvenido 🙂

No abuses de $(document).ready

Una de las primeras cosas que aprendemos en cualquier tutorial de jQuery es cómo y por qué utilizar $(document).ready y $() para inicializar nuestro código, en lugar del tradicional window.onload y su contrapartida en jQuery, $(window).load.

$(document).ready(function() {
  // La magia ocurre aquí
});

$(function() {
  // La magia ocurre aquí
});


window.onload = function() {
  // La magia ocurre aquí
} 

$(window).load(function(){
   // La magia ocurre aquí
});

La principal diferencia entre ambos métodos es que el evento ready se lanza cuando el navegador termina de cargar el árbol del documento (evento DOMContentLoaded en los navegadores más modernos), mientras que el manejador de onload se ejecuta cuando se termina de cargar todo el documento, incluidas las imágenes y los iframes.

Consecuentemente, habitualmente nos interesará colocar el código de inicialización en el manejador del evento ready, de forma que nuestra página parezca más rápida, al no tener que esperar el usuario a que cargue toda la página para poder usar la funcionalidad.

Sin embargo, hay ocasiones en las que el uso de $(document).ready puede ser contraproducente. Es el caso de funcionalidades que no son prioritarias, que pueden estar retrasando la carga de otras características más importantes, y que podemos postergar hasta después del evento onload.

Otro caso es el binding de eventos con live y delegate, funciones introducidas en las versiones 1.3 y 1.4.2 y que permiten agregar manejadores de eventos a elementos que aún no existen en el DOM. Si agregamos un manejador para aumentar el tamaño de la fuente de un párrafo al hacer clic sobre él y lo colocamos en el manejador de $(document).ready, tendremos que esperar a que cargue todo el DOM, mientras que si lo sacamos del manejador del evento ready, la funcionalidad estará activa en cuanto se cargue el elemento.

<html>
  <head>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.2.min.js"></script>
    <script type="text/javascript">
      $('#mensaje').live('click', aumentarTamanyo);
      
      function aumentarTamanyo() {
        $(this).css('font-size', '200%');
      }
    </script>
  </head>

  <body>
    <p id="mensaje">jQuery: The Write Less, Do More, JavaScript Library</p>
  </body>
</html>

preventDefault, stopPropagation y return false

Otra de las cosas que se aprenden en los tutoriales de jQuery y que tampoco solemos cuestionar es el devolver false en un manejador para evitar que el navegador ejecute el comportamiento por defecto para el evento generado por el usuario. Podemos devolver false en el manejador del evento click de un enlace para evitar que se cargue la página enlazada, por ejemplo, o en el manejador de submit de un formulario para evitar que se envíe.

Esto último es muy común en funciones de validación de formularios:

<html>
  <head>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.2.min.js"></script>
    <script type="text/javascript">
      $(function() {
        $('#frmLogin').submit(comprobarCampos);
      });
      
      function comprobarCampos() {
        if(!$('#usuario').val() || !$('#password').val()) {
          alert('Por favor, introduzca su nombre de usuario y contraseña');
          return false;
        }
      }
    </script>
  </head>

  <body>
    <form id="frmLogin" action="login" method="post">
      <label for="usuario">Usuario</label>: <input type="text" name="usuario" id="usuario"/>
      <label for="password">Contraseña</label>: <input type="password" name="password" id="password"/>
      <input type="submit" value="Entrar"/>
    </form>
  </body>
</html>

El ejemplo funciona perfectamente, y return false parece estar haciendo lo que queríamos. Sin embargo, en realidad, al devolver un valor false en el manejador, lo que estamos haciendo, además de prevenir la ejecución del comportamiento por defecto del navegador, es evitar que el evento se propague y que, por lo tanto, se ejecute el código de los manejadores de los padres para este evento.

Si sólo queremos evitar el comportamiento por defecto lo correcto es llamar a la función preventDefault() del objeto event. De igual manera, si sólo queremos evitar que se propague a los padres, llamaremos a la función stopPropagation() del objeto event.

<html>
  <head>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.2.min.js"></script>
    <script type="text/javascript">
      $(function() {
        $('#frmLogin').submit(comprobarCampos);
      });
      
      function comprobarCampos(event) {
        if(!$('#usuario').val() || !$('#password').val()) {
          alert('Por favor, introduzca su nombre de usuario y contraseña');
          event.preventDefault();
        }
      }
    </script>
  </head>

  <body>
    <form id="frmLogin" action="login" method="post">
      <label for="usuario">Usuario</label>: <input type="text" name="usuario" id="usuario"/>
      <label for="password">Contraseña</label>: <input type="password" name="password" id="password"/>
      <input type="submit" value="Entrar"/>
    </form>
  </body>
</html>

each y map para sustituir bucles

Aunque un bucle nativo siempre será más rápido, los tiempos por los que tenemos que preocuparnos en primer lugar son los de desarrollo y mantenimiento. Por este motivo, en ocasiones, es posible que prefieras utilizar las funciones $.each, $.fn.each, $.map y $.fn.map en lugar de usar bucles.

Aunque el siguiente ejemplo no es lo bastante complejo como para obtener ningún tipo de beneficio al utilizar estas funciones, os servirá para ver el aspecto de cada una de las alternativas

// Bucle for
var numeros = [1, 2, 3];
var numItems = numeros.length;
for(var i = 0; i < numItems; i++) {
  numeros[i] = numeros[i] * numeros[i];
}


// Bucle for in
var numeros = [1, 2, 3];
for(var i in numeros) {
  numeros[i] = numeros[i] * numeros[i];
}


// Bucle for in con la lógica en una función
var numeros = [1, 2, 3];
for(var i in numeros) {
  numeros[i] = cuadrar(numeros[i]);
}

function cuadrar(num) {
    return num * num;
}


// $.each
var numeros = [1, 2, 3];
$.each(numeros, function(i, val) {
    numeros[i] = val * val;
});


// $.map
var numeros = [1, 2, 3];
numeros = $.map(numeros, function(val) {
    return val * val;
});


// $.map con la lógica en una función
var numeros = [1, 2, 3];
numeros = $.map(numeros, cuadrar);

function cuadrar(num) {
    return num * num;
}

Los bucles y los each no siempre son necesarios

jQuery intenta facilitarnos trabajar con las colecciones de elementos. No es necesario que hagas algo así para ocultar todos los enlaces:

// Con $.fn.each
$('a').each(function () {
    $(this).hide();
});

// Con for in
var enlaces = $('a');
for(var i in enlaces) {
  $(enlaces[i]).hide();
};

basta, simplemente, con que hagas esto:

$('a').hide();

HTML elegante

Aunque un script no es lugar adecuado para tener nuestro código HTML, en ocasiones queremos crear nuevos elementos que añadir al DOM y no queremos o no podemos utilizar plantillas. La manera más cándida de hacer esto sería algo como:

$('<img src="http://www.jquery.com/logo.png" alt="Logo de jQuery" id="logo" onclick="alert(\'Este es el logo de jQuery\')"/>')

O una forma más «a lo jQuery»

$('<img/>').attr('src', 'http://www.jquery.com/logo.png').
  attr('alt', 'Logo de jQuery').
  attr('id', 'logo').
  click(function() {
    alert('Este es el logo de jQuery')
  });

Pero desde jQuery 1.4 tenemos una forma aún mejor de hacer esto, pasando como segundo argumento un objeto cuyas propiedades son los nombres de los atributos, y al que podemos pasar también funciones de jQuery:

$("<img/>", {
  src: 'http://www.jquery.com/logo.png',
  id: 'logo',
  click: function() {
    alert('Este es el logo de jQuery')
  }
});

Encadenado y caché de selecciones

Cada vez que utilizamos un selector en nuestro código, el engine de jQuery tiene que realizar operaciones más o menos costosas dependiendo del soporte nativo del navegador. Un selector por identificador, por ejemplo, es bastante rápido, dado que utiliza document.getElementById por debajo; selectores más complejos, como las pseudoclases, requieren de un mayor tiempo y mayores recursos para su procesado.

Lo ideal es no abusar demasiado de las búsquedas en el DOM y guardar el resultado en un variable, o bien aprovechar que las funciones suelen devolver la propia colección y utilizar cadenas de funciones.

// Mala idea, desde el punto de vista del rendimiento
$('a:not([href*=mundogeek.net])').attr('target', '_blank');
$('a:not([href*=mundogeek.net])').css('background-image', 'url("/img/externo.gif")');

// Cacheando en una variable
var enlacesExternos = $('a:not([href*=mundogeek.net])');
enlacesExternos.attr('target', '_blank');
enlacesExternos.css('background-image', 'url("/img/externo.gif")');

// Usando cadenas de funciones
$('a:not([href*=mundogeek.net])')
  .attr('target', '_blank')
  .css('background-image', 'url("/img/externo.gif")')

data en lugar de rel

En ocasiones, y aunque no es recomendable abusar de ello, es posible que nos interese almacenar cierta información en el propio DOM. Esto se venía haciendo hasta ahora utilizando hacks como el uso dudoso del atributo rel, las clases, o los atributos con nombres inventados, que generaban HTML no válido.

Esto cambiará en HTML 5, con la introducción de los nuevos atributos data, pero jQuery viene ofreciendo desde su versión 1.2.3 un sistema aún mejor, que evita tener que interactuar con el DOM, pero que también soporta la lectura de estos nuevos atributos de HTML 5. Este fantástico método se llama, como era de esperar, data y su uso es de lo más sencillo:

$('table#tabla').data({
  campoOrden: 'id',
  orden: 'asc'
});

$('table#tabla').data('campoOrden');

Agrupa las operaciones

¿Necesitas aplicar un mismo método a varios elementos? No es necesario que los selecciones uno por uno. Si los agrupas utilizando un único selector, irás más rápido y el código será más corto.

// Tedioso
$('#formularioCrear').show();
$('#tablaItems').show();
$('#mensajeAlerta').show();

// Mucho mejor
$('#formularioCrear, #tablaItems, #mensajeAlerta').show();

Evita conflictos en tus plugins

Crear plugins para jQuery es tan sencillo que muchos desarrolladores se encuentran escribiendo sus propios plugins a los pocos días de empezar a utilizar la librería. Si quieres distribuir tus extensiones, es una buena idea utilizar una sintaxis como la siguiente, para evitar conflictos con otras librerías que puedan utilizar $ para otros fines:

(function($) {
  $.alertar = function(mensaje) {
    alert(mensaje);
  }
})(jQuery);

Aunque el código puede parecer un poco confuso a simple vista, si lo analizamos detenidamente, veremos que no tiene mayor misterio. No hacemos más que crear una función anónima a la que pasamos como parámetro el objeto jQuery, dando a este parámetro el nombre $, para poder utilizar este símbolo indistintamente dentro de la función.




21 comentarios en «Buenas prácticas con jQuery»

    1. La verdad que sí, cuando menos curioso :-).

      He de reconocer que en varias ocasiones para mostrar datos de una consulta he usado la primera opción (poner el churro al completo) 🙁 aunque inicialmente intenté ponerlo como la segunda opción, pero por misterios de la naturaleza no lo conseguí.

  1. Sublime!
    Me gustó mucho cuando empecé a usar jquery No he ahondado mucho en cosas elaboradas, solo cuestiones básicas.
    Aún así este artículo de los mejores que he visto al respecto.

  2. Pingback: Unity. Simplify Your Life, cambios recientes en Ubuntu 11.10 (video), bonita pantalla de cuentas de usuario en Ubuntu 11.10 y algunos Breves « Ubuntu Life

  3. Donde se inserta el siguiente codigo? o que es lo que ocurre. Muy buena la entrada por cierto 🙂
    $(«<img/>», {
    src: ‘http://www.jquery.com/logo.png’,
    id: ‘logo’,
    click: function() {
    alert(‘Este es el logo de jQuery’)
    }
    });

    1. Lo que haces con ese código es crear un elemento HTML en tiempo de ejecución, lo que puede ser útil para añadirlo al DOM en respuesta a algún evento.
      Por ejemplo, podrías tener una caja de texto para introducir el nombre de tu blog en una página de perfil de usuario, y un botón para añadir otra caja de texto para el caso de que tenga más de un blog.

  4. Pues he pecado de cándido jaja. Cómo se haría para pasar un nuevo elemento ‘elegantemente’ a $(body).append()?

    Ahora mismo le paso un literal HTML.

  5. Excelente articulo, ya esta en mi lista de Favoritos… muy buenos concejos, podrias ampliar el articulo con mas concejos sobre las nuevas versiones de jquery.
    Saludos.

Responder a Pablo Cancelar respuesta

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.