Los nuevos métodos jQuery on() y off()

21 Nov 2011

Introducción

Con la llegada de jQuery 1.7, se han solucionado varios bugs que esta biblioteca arrastraba de versiones anteriores; también se ha mejorado una vez más el rendimiento general de la misma prestándose especial atención a selectores y a la portabilidad a los dispositivos móviles.

Pero además de estos ‘arreglos’, tenemos nuevos métodos y nuevas funcionalidades interesantes. De entre los primeros, sin duda los más interesantes son los que se refieren a la nueva gestión de eventos: el corazón de jQuery.

Los eventos en jQuery

Desde su aparición, jQuery se ha caracterizado por la flexibilidad con la que el desarrollador puede efectuar selecciones de elementos dentro del DOM y por la facilidad para asociar eventos a cada uno de esos conjuntos seleccionados.

Sin embargo, un problema con el que han tenido que lidiar los chicos del jQuery Team es que, con el paso de las versiones, estos manejadores (handlers) se han multiplicado hasta resultar confusos en las últimas releases.

Al principio, solo disponíamos del viejo método bind con el que asignábamos un determinado evento a un objeto jQuery:

$('mySelector').bind('click', function(){
  // The callback goes here...
})

Para aquellos eventos muy frecuentes, como en el caso del click anterior, contábamos incluso con prácticos atajos (shortcuts):

$('mySelector').click( function(){
  // The callback goes here...
});

El problema de bind es que solo funciona con aquellos elementos del DOM que ya están presentes cuando la función se ejecuta. Es decir, aquellos otros elementos que pudieran añadirse de forma dinámica, no heredan este comportamiento.

Para solventar este problema, apareció en escena el método live(). Sin embargo, live presentaba más problemas que ventajas y rápidamente se consideró obsoleto (deprecated). El problema con este método proviene de su bajo rendimiento en estructuras complejas y de algunos comportamientos impredecibles que se dan en determinados escenarios. Para un completo artículo sobre el porqué no debemos usar jQuery live, podemos leer un artículo escrito hace algún tiempo en este blog sobre ese respecto.

Buscando una vía alternativa al uso de live, jQuery ofreció un nuevo método muy similar: delegate(). Hasta el momento, delegate ha tratado de cubrir la funcionalidad de asociar eventos a elementos DOM pero, de nuevo, el resultado no ha sido todo lo satisfactorio que cabría esperar.

Es por todo esto que, frente a la confusión por parte del desarrollador de encontrarse tantos métodos similares (bind, live, delegate, one, …), el jQuery Team ha decidido crear dos nuevos métodos que aúnen el comportamiento de todos ellos buscando el máximo rendimiento y minimizando la confusión: on() y off().

El método on()

El objetivo de on() es reemplazar a los antiguos bind, live y delegate. Su sintaxis es la siguiente:

$(elements).on(events [, selector] [, data], handler);

Donde:

  • events son los eventos que se buscan asociar al conjunto de elementos seleccionados. La novedad aquí es que pueden asociarse más de un evento separándolos con espacios. Por ejemplo: ‘click’, ‘click blur’.
  • selector especifica los descendientes de los elementos seleccionados que dispararán el evento. Se trata de un parámetro opcional.
  • data indica cualquier tipo de datos que se necesite pasar al manejador cuando se dispara el evento. Es también un parámetro opcional y, por lo general, se corresponde con un objeto jQuery.
  • handler se corresponde con el callback o acción a realizar después de que el evento se dispare.

Un ejemplo de uso completo puede ser el siguiente:

$('#myContainer .item').on({
  click: function() {
    event.preventDefault();
    console.log('item clicked');
  },
  mouseenter: function() {
    console.log('enter!');
  }
});

El resultado del código anterior es que hemos asociado dos eventos diferentes, con dos callbacks diferentes a aquellos elementos DOM que cumplan con el criterio de la selección (en este caso aquellos con la clase ‘item’ descendientes de un ID ‘myContainer’). Como hemos comentando más arriba, al reemplazar a live() y delegate(), este comportamiento será heredado por cualquier otro elemento que se inserte en el DOM de forma dinámica aún después de haber sido ejecutada la función anterior.

Actualizaciones desde métodos antiguos

Actualizar nuestros viejos códigos a la nueva versión resulta por lo general sencillo:

// Old live() style:
$('#myContainer .item').live('click', function(event) {
  event.preventDefault();
  console.log('item clicked');
});
 
// New on() style:
$('#myContainer').on('click', '.item', function(event) {
  event.preventDefault();
  console.log('item clicked');
});

En el ejemplo anterior hemos visto además, como se asocian selectores dentro del propio cuerpo de la función para especificar aquellos descendientes que dispararán el evento.

// Old delegate() style:
$('#myContainer').delegate('.item', 'click', function(event) {
  event.preventDefault();
  console.log('item clicked');
});
 
// New on() style:
$('#myContainer').on('click', '.item', function(event) {
  event.preventDefault();
  console.log('item clicked');
});

En el caso de la actualización desde delegate, solo necesitamos cambiar el orden de los parámetros.

El método off()

Al igual que con la asocación de eventos, la confusión de métodos también llegaba con la acción contraria: eliminarlos.

Para esta acción, contábamos con varios métodos como unbind(), die() o undelegate(). De nuevo, el objetivo principal de la nueva instrucción es reemplazarlos a todos de un modo consistente.

La sintaxis de off() resulta similar a la de on():

$(elements).off( [ events ] [, selector] [, handler] );

Con off(), todos los parámetros son opcionales. Cuando se utiliza en su forma más simple, $(elements).off(), se eliminan todos los eventos asociados al conjunto seleccionado.

Actualizaciones desde métodos antiguos

Como en el caso anterior, dependiendo de qué método vengamos, la conversión presenta más o menos cambios.

Para el caso de unbind(), solo necesitamos cambiar el nombre del método:

// Old unbid() style:
$('#container a').unbind('click');
 
// New off() style:
$('#container a').off('click');

Para aquellos códigos que hagan uso del live() / die(), el cambio es similar:

// Old die() style:
$('#container a').die('click');
 
// New off() style:
$('#container').off('click', 'a');

De nuevo, solo es necesario organizar ligeramente el orden de los parámetros para aprovechar la nueva potencia del método off().

Para la portabilidad desde undelegate(), solo tenemos que cambiar el orden de los parámetros:

// Old undelegate() style:
$('#container a').undelegate('a', 'click');
 
// New off() style:
$('#container a').off('click', 'a');

Conclusión

Con la nueva versión 1.7 de jQuery, se trata por fin de poner algo de orden en cuanto al manejo de eventos que ofrece esta biblioteca, aunando en unos mismos métodos toda la funcionalidad que hasta la fecha se había ido desplegando en varios.

Con esto, el resultado es un API más robusto y sólido, exactamente lo que los desarrolladores jQuery han solicitado al equipo de desarrollo.

Más:

{18} Comentarios.

  1. Pablo

    Carlos muchas gracias por el artículo.
    Otra novedad que me ha parecido útil es el método isNumeric() para verificar si es o no un número y el nuevo objeto de Callbacks(). Un saludo.

  2. Mounttroll

    Siempre es bueno cuando abro la web y encuentro un nuevo post vuestro.
    Muchas gracias, buenas novedades por parte del equipo de jQuery.

  3. Fernando Correa

    Muy buen articulo sobre esta mejora en funcionalidad de jQuery, muchas gracias por tus aportes.

  4. Guillote Paz

    Carlos muy buen artículo! Gracias por compartirlo. De todos modos, hay una cosa que no me queda clara en cuanto a la migración de los nuevos eventos.

    Como puedo reproducir la funcionalidad del viejo .one() con los nuevos eventos? No estoy encontrando esto en ningún lugar y capaz que ya lo han probado.

    Muchas Gracias!

    Slds,

  5. Guillote Paz

    Buenas,

    Encontré que el método .one se mantiene en la 1.7, pero se actualizó:

    .one( events [, selector] [, data], handler )

    http://api.jquery.com/one/

    Slds,

    • Carlos Benítez

      Si, el método once() se ha actualizado, por lo que podemos continuar utilizándolo.
      Gracias!

  6. WidPlay

    Sin duda algo que todos los que desarrollamos utilizando el framework JQuery estabamos esperando!!! Tengo ya muchas ganas de implementar esta version en WidPlay con estas mejoras y ver que tal el rendimiento.

    Por cierto Carlos, sabes alguna forma de que podamos medir el rendimiento de una aplicacion al detalle y saber si cuando hacemos algo en js o jquery a sido para mejor o para peor?

    Un saludo!!

  7. LBM

    Hola, buena explicación, siempre digo, nunca te acostarás sin saber una cosa más. Ahora a descubrir por qué no se debe utilizar live con jquery.

  8. Andres

    Sigo sin entender por que una cosa es mejor que otra, como puede hacerse la comprobación… Por mucho que se diga… ¿Quien comprueba estas cosas? Como ha dicho WidPlay, ¿Como se testea eso?

    • Carlos Benítez

      Testear el rendimiento de distintas funciones o rutinas en Javascript es sencillo si cuentas con herramientas como, por ejemplo, Firebug. Ahí puedes insertar en el código ‘timers’ que se ejecutan antes y después de la función en cuestión devolviéndote el tiempo en milisegundos que ha llevado realizar determinada acción. Podemos ver cómo funciona en un artículo anterior en el que trato sobre el uso de la consola:

      La consola de Firebug al detalle

      Si se prefiere hacer de forma manual, pues hay que calcular el tiempo transcurrido entre la llamada a una función y su conclusión mediante métodos tradicionales como son el pedir la hora en cada punto y restarlas más tarde obteniendo así el intervalo.

      Un ejemplo de esto último podría ser:

      var start = new Date().getTime();
      
      for (i = 0; i < 50000; ++i) {
        // do something
      }
      
      var end = new Date().getTime();
      var time = end - start;
      
      console.log('Execution time: ' + time);
      

      En el ejemplo anterior, estamos testeando el tiempo que tardamos en iterar n veces por un determinado bloque de código. Si en lugar del bucle, llamamos directamente a otra función, obtendríamos una métrica igualmente valiosa.

      Saludos!

  9. widplay

    Valla no sabia esto de Firebug! :O, aun no se como voy a poder comparar el on y off con el live, solo por curiosidad pero veré como me las apaño.
    Gracias!

  10. Hola vi tus articulos sobre estos metodos jquery ya que estaba teniendo problemas al utilizarlos en un sistema bastante complejo en el cual reutilizo muchas veces el mismo widget, por ejemplo la aplicacion tiene un menu en el segun la opcion seleccionada del menu este trae una tabla, cada tabla tiene una columna con dos img, que las utilizo para editar o borrar el registro de dicha tabla, para ello lo hacia con los metodos live, luego delegate y por ultimo on, pero en todos los casos verificando con firebug, cada vez q vuelvo a hago click sobre el img se llama o se dispara el evento sobre el img tantas veces como haya entrado a esa opcion del menu.
    Levo mucho tiempo con este problema, estuve leyendo documentacion de jquery y varios blog, en español e ingles y aun no consigo solucionarlo. Seria de gran ayuda que me des una mano, tal vez tengo algunos conceptos equivocados no se la verdad que podria ser.
    De ser necesario tengo una version demo en mi sitio web si quieres te paso la url asi la miras.
    Desde ya muchisimas gracias y Saludos.

  11. writkas

    hola soy novato en jquery

    donde puedo encontrar una lista de los eventos que están disponibles ? es que me gustaría saber si existe algún evento que se lance en respuesta a que se actualice y cargue un trozo de código html, por ejemplo cada vez que se actualice lo que hay dentro de un por ejemplo. no se si me entienden xD

    un saludo

    😀

    • Carlos Benítez

      Hola;
      tienes toda la documentación sobre eventos en su página oficial:

      http://api.jquery.com/category/events/

      En tu caso, si el contenido en el DIV se inyecta por AJAX, necesitas los callbacks del método; si el evento que estás buscando es parte de un IFRAME, entonces quizá te sirva .load()
      En realidad, depende de dónde llegue el contenido de tu DIV; revisa todo el listado porque seguro que encuentras el evento apropiado.

      Saludos

  12. writkas

    ok, ya lo logre gracias.

    me sirvió el evento change() pero no se cargaba el código dentro de ese evento cuando eliminaba los elementos de mi div, así que utilice un trigger() para que se dispare el evento change() al hacer click en un botón, etc…

    tal vez no sea una solución elegante .. pero me funcionó.

    saludos

  13. Eduardo

    Hola Carlos. Cual es la diferencia de event.preventDefault() y event.StopPropagation()

  14. Diego Jose Molina Gonzalez

    Me podrían decir como aplico un método para transformar esto a un delegate

    $(“.boxscroll”).niceScroll({cursorborder:”rgb(185, 185, 185)”,cursorcolor:”#444″,boxzoom:false, cursorwidth:9})

    ya que me deja el scroll (estilo facebook) sólo en los divs que se crean con el DOM, pero cuando creo un div dinámico no es reconocido y no toma el efecto del scroll…

    Si alguien sabe como lo agradecería

Deja un comentario

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