Cacheando objetos en jQuery

24 Dic 2010

Cuando utilizamos jQuery para la selección y manipulación de elementos del DOM, una práctica que a menudo olvidan los programadores es cachear los objetos.

Tomemos el siguiente código de ejemplo:

$('#myObjId').click(function() {
  if ( $('#myObjId').hasClass('clicked') ) {
    $('#myObjId').removeClass('clicked');
    $('#myObjId').css('background-color', 'red');
  } else {
    $('#myObjId').addClass('clicked');
    $('#myObjId').css('background-color', 'blue');
  }
);
$('#myObjId').val('Click Me!');

Con este fragmento comprobamos cómo cada vez que se realiza una acción, el selector interno de jQuery (Sizzle), debe recorrer el DOM completamente hasta encontrar el elemento sobre el que estamos operando, lo cual, dependiendo de la complejidad del sitio o de las acciones, puede suponer una reducción importante en el rendimiento.

Método clásico

Una forma de evitar ese coste es simplemente cacheando el objeto y luego operar directamente sobre su referencia, evitando así que jQuery tenga que buscarlo cada vez que lo necesitemos:

var $myObj = $('#myObjId');
$myObj.click( function(){
  if ( $myObj.hasClass('clicked') ){
    $myObj.removeClass('clicked');
    $myObj.css('background-color', 'red');
  } else {
    $myObj.addClass('clicked');
    $myObj.css('background-color', 'blue');
  }
});
$myObjId.val('Click Me!');

Al asignar una variable al selector, ésta referencia directamente al objeto jQuery, por lo que podemos operar sobre él con los métodos de la librería. Para recordar que se trata de un objeto jQuery, una buena práctica es añadir la ‘$’ delante del nombre de la variable.

Método mejorado

Todavía podemos mejorar el código anterior mediante el encadenado (chaining) de acciones sobre un mismo objeto:

var $myObj = $('#myObjId');
$myObj.click(function(){
  if ( $myObj.hasClass('clicked') ){
     $myObj.removeClass('clicked').css('background-color', 'red');
  } else {
    $myObj.addClass('clicked').css('background-color', 'blue');
  }
});
$myObjId.val('Click Me!');

De este modo, si tenemos que actuar sobre un mismo objeto con varias acciones consecutivas, no tendremos que recorrer el DOM una y otra vez ganando rendimiento.

Cacheando el objeto en funciones anónimas

Cuando aplicamos un callback a un evento, podemos cachear el objeto que lo recibe mendiante el comando this:

var $myObj = $('#myObjId');
$myObj.click( function(){
  var $this = $(this);
  if ( $this.hasClass('clicked') ){
    $this.removeClass('clicked').css('background-color', 'red');
  } else {
    $this.addClass('clicked').css('background-color', 'blue');
  }
} );
$myObjId.val('Click Me!');

De esta forma, no es necesario volver a buscar el objeto en el DOM con lo que de nuevo ahorramos recursos. De nuevo, para recordar que se trata de un objeto jQuery, es interesante utilizar un prefijo como ‘$‘.

Método Experto

Finalmente, es importante conocer el comportamiento interno de jQuery para optimizar al máximo nuestros selectores: cuando aplicamos una acción sobre un selector, jQuery nos devuelve de forma interna el propio objeto. Esto permite refactorizar el código anterior y mejorarlo:

var $myObj = $('#myObjId').click(function(){
  var $this = $(this);
  if ( $this.hasClass('clicked') ){
    $this.removeClass('clicked').css('background-color', 'red');
  } else {
    $this.addClass('clicked').css('background-color', 'blue');
  }
}).val('Click Me!');

En este caso, hemos realizado las operaciones sobre la misma declaración de la variable, seguros de que el objeto devuelto es siempre la referencia original. De este modo, aún conservamos la referencia al objeto jQuery para usarlo más adelante en nuestro código.

Más:

{9} Comentarios.

  1. fcodiaz

    una de las cosas que siempre se olvida y creo que es una de las cosas vitales para la optimizacion de recursos en jQuery, en esta cosita me fijo y me doy cuenta si estoy frente a un desarrollador experimentado o un novato

  2. J SILVA

    Hola me gusta cachar objetos tal como lo expones en tu artículo, de hecho aprendi una parte de aquí, sin embargo, hay algunos casos donde no se como debe escribirse el codigo, te pongo un ejemplo:
    $(“#div_x p”), puedo cachar el elemento padre con esto var $div_x=$(“#div_x”),
    pero no se como puedo cachar al segundo objeto o manipularlo si ya lo tuviera cachado.

    Otro problema es este var $elemento1=$(“#elemento1), $elemento2=$(“#elemento2”);
    $elemento1.$elemento2.click(etc… seria igual $(“#elemento1,#elemento2”).click(etc….
    Gracias de antemano y si encuentro la respuesta antes la pego aqui en tu post.

    • Carlos Benítez

      Hola;
      no sé si he comprendido bien tu primera pregunta, pero te desarrollo el ejemplo:

      // El selector jQuery: $('#div_x p') puede cachearse por separado así:
      var div_x = $('#div_x');
      var div_x_p = div_x.find('p');
      

      La idea es que ‘div_x’ es un objeto jQuery y, por tanto, acepta los métodos de la biblioteca para crear una nueva colección de objetos:

      var div_x = $('#div_x'); // Cacheo #div_x
      var parent = div_x.parent(); // Busco el padre.
      var next_element = div_x.next(); // Busco el siguiente elemento en el DOM
      var closest_p = div_x.next('p'); // Busco entre los hijos de #div_x, el primer párrafo.
      

      En cuanto a tu segundo problema, la sintaxis no sería correcta: estarías cogiendo el primer objeto (elemento1) para tratar de acceder a un método correspondiente a $elemento2. Ten en cuenta que, en Javascript, la notación por punto ‘.’ es para acceder a un método del objeto de la izquierda. Por eso funciona por ejemplo ‘click’, porque está definido dentro de jQuery como un método directo de esta biblioteca.

      Para conseguir el comportamiento que buscas, la sintaxis correcta sería:

      ( element1, element2 ).click( function(){ /* ... */ } );
      

      La sintaxis puede escribirse también en un estilo ‘más jQuery’:

      element1.add( element2 ).click( function(){ /* ... */ } );
      

      Espero que te sirva; un saludo!

  3. J SILVA

    GRACIAS POR TOMAR SU TIEMPO PARA RESPONDER.

  4. dev001

    muy buena explicacion..

  5. @JonatanEsteche_

    Muchas gracias! ha sido muy útil este ejemplo, lo empezaré a aplicar a mis proyectos para que mejore el rendimiento.

  6. savin

    Muy buen post pero no consigo solucionar mi problema,
    tengo un par de funciones en jquery en las que utilizo el operador $(this) para acceder a unos campos de texto.y mostrarlos por pantalla y cambiar estilos.

    El problema es que tienen que ambos “this” independientes y cuando voy a uno tambien modifica el otro. Seguramente sea una tontería pero llevo con esto más de 1hora.

    Gracias

    • Carlos Benítez

      Si puedes poner el código de tus funciones y botones, quizá podamos echarte una mano.

      Saludos!

  7. jrcsdev

    Hermano déjame felicitarle por tan valioso recurso. No hablo solo de este post, hablo de todo el sitio. Tengo meses aprendiendo jQuery y Javascript avanzado gracias a etnasoft… Gracias por compartir tu conocimiento, ya que algunas personas (como yo) no son tan diestras con el inglés para buscar buenas documentaciones. Saludos desde Venezuela. Eres un crack!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *