¿Deberían todos los eventos jquery estar vinculados a $ (documento)?

164

De donde viene esto

Cuando aprendí jQuery por primera vez, normalmente adjunto eventos como este:

$('.my-widget a').click(function() {
    $(this).toggleClass('active');
});

Después de aprender más sobre la velocidad del selector y la delegación de eventos, leí en varios lugares que "la delegación de eventos jQuery hará que su código sea más rápido". Entonces comencé a escribir código como este:

$('.my-widget').on('click','a',function() {
    $(this).toggleClass('active');
});

Esta también fue la forma recomendada para replicar el comportamiento del evento .live () en desuso. Lo cual es importante para mí ya que muchos de mis sitios agregan / eliminan dinámicamente widgets todo el tiempo. Sin embargo, lo anterior no se comporta exactamente como .live (), ya que solo los elementos agregados al contenedor ya existente '.my-widget' obtendrán el comportamiento. Si agrego dinámicamente otro bloque de html después de que se haya ejecutado ese código, esos elementos no tendrán los eventos vinculados a ellos. Me gusta esto:

setTimeout(function() {
    $('body').append('<div class="my-widget"><a>Click does nothing</a></div>');
}, 1000);


Lo que quiero lograr:

  1. El antiguo comportamiento de .live () // significa asociar eventos a elementos aún no existentes
  2. los beneficios de .on ()
  3. rendimiento más rápido para vincular eventos
  4. Manera simple de gestionar eventos

Ahora adjunto todos los eventos como este:

$(document).on('click.my-widget-namespace', '.my-widget a', function() {
    $(this).toggleClass('active');
});

Lo que parece cumplir con todos mis objetivos. (Sí, es más lento en IE por alguna razón, ¿no se sabe por qué?) Es rápido porque solo un evento está vinculado a un elemento singular y el selector secundario solo se evalúa cuando se produce el evento (corríjame si esto está mal aquí). El espacio de nombres es increíble, ya que hace que sea más fácil alternar el escucha del evento.

Mi solución / pregunta

Así que estoy empezando a pensar que los eventos jQuery siempre deberían estar vinculados a $ (documento).
¿Hay alguna razón por la que no quieras hacer esto?
¿Podría considerarse esto una mejor práctica? Si no, ¿por qué?

Si has leído todo esto, gracias. Agradezco cualquier / todos los comentarios / ideas.

Suposiciones

  1. Usando jQuery que admite .on()// al menos la versión 1.7
  2. Desea que el evento se agregue al contenido agregado dinámicamente

Lecturas / ejemplos:

  1. http://24ways.org/2011/your-jquery-now-with-less-suck
  2. http://brandonaaron.net/blog/2010/03/4/event-delegation-with-jquery
  3. http://www.jasonbuckboyer.com/playground/speed/speed.html
  4. http://api.jquery.com/on/
Dólar
fuente

Respuestas:

215

No, NO debe vincular todos los controladores de eventos delegados al documentobjeto. Ese es probablemente el peor escenario que podría crear.

En primer lugar, la delegación de eventos no siempre acelera su código. En algunos casos, es ventajoso y en otros casos no. Debe usar la delegación de eventos cuando realmente necesite delegación de eventos y cuando se beneficie de ella. De lo contrario, debe vincular los controladores de eventos directamente a los objetos donde ocurre el evento, ya que esto generalmente será más eficiente.

En segundo lugar, NO debe vincular todos los eventos delegados a nivel de documento. Esto es exactamente por qué .live()fue desaprobado porque esto es muy ineficiente cuando tienes muchos eventos vinculados de esta manera. Para el manejo de eventos delegados, es MUCHO más eficiente vincularlos al elemento primario más cercano que no sea dinámico.

Tercero, no todos los eventos funcionan o todos los problemas pueden resolverse con delegación. Por ejemplo, si desea interceptar eventos clave en un control de entrada y bloquear que no se ingresen claves no válidas en el control de entrada, no puede hacerlo con el manejo de eventos delegados porque para cuando el evento llegue al controlador delegado, ya ha procesado por el control de entrada y es demasiado tarde para influir en ese comportamiento.

Aquí hay momentos en que la delegación de eventos es obligatoria o ventajosa:

  • Cuando los objetos en los que está capturando eventos se crean / eliminan dinámicamente y aún desea capturar eventos en ellos sin tener que volver a vincular explícitamente los controladores de eventos cada vez que crea uno nuevo.
  • Cuando tiene muchos objetos que todos quieren exactamente el mismo controlador de eventos (donde lotes es al menos cientos). En este caso, puede ser más eficiente en el momento de la configuración enlazar un controlador de eventos delegado en lugar de cientos o más controladores de eventos directos. Tenga en cuenta que el manejo de eventos delegados siempre es menos eficiente en tiempo de ejecución que los controladores de eventos directos.
  • Cuando intentas capturar (en un nivel superior de tu documento) los eventos que ocurren en cualquier elemento del documento.
  • Cuando su diseño utiliza explícitamente el burbujeo de eventos y stopPropagation () para resolver algún problema o característica en su página.

Para entender esto un poco más, uno necesita entender cómo funcionan los controladores de eventos delegados de jQuery. Cuando llamas a algo como esto:

$("#myParent").on('click', 'button.actionButton', myFn);

Instala un controlador de eventos genérico jQuery en el #myParentobjeto. Cuando un evento de clic aparece en este controlador de eventos delegado, jQuery tiene que revisar la lista de controladores de eventos delegados adjuntos a este objeto y ver si el elemento de origen del evento coincide con alguno de los selectores en los controladores de eventos delegados.

Debido a que los selectores pueden estar bastante involucrados, esto significa que jQuery tiene que analizar cada selector y luego compararlo con las características del objetivo del evento original para ver si coincide con cada selector. Esta no es una operación barata. No es gran cosa si solo hay uno de ellos, pero si pones todos tus selectores en el objeto del documento y hay cientos de selectores para comparar con cada evento burbujeado, esto puede comenzar a obstaculizar el rendimiento del manejo de eventos.

Por esta razón, desea configurar sus controladores de eventos delegados para que un controlador de eventos delegado esté tan cerca del objeto de destino como sea práctico. Esto significa que menos eventos aparecerán en cada controlador de eventos delegado, mejorando así el rendimiento. Poner todos los eventos delegados en el objeto del documento es el peor rendimiento posible porque todos los eventos burbujeados tienen que pasar por todos los controladores de eventos delegados y ser evaluados contra todos los posibles selectores de eventos delegados. Esto es exactamente por qué .live()está en desuso porque esto es lo que .live()hizo y resultó ser muy ineficiente.


Entonces, para lograr un rendimiento optimizado:

  1. Solo use el manejo delegado de eventos cuando realmente proporciona una característica que necesita o aumenta el rendimiento. No lo use siempre porque es fácil porque cuando realmente no lo necesita. Realmente funciona peor en el momento del envío del evento que el enlace directo del evento.
  2. Adjunte los controladores de eventos delegados al padre más cercano a la fuente del evento como sea posible. Si está utilizando la gestión de eventos delegada porque tiene elementos dinámicos para los que desea capturar eventos, seleccione el elemento primario más cercano que no sea dinámico.
  3. Utilice selectores fáciles de evaluar para controladores de eventos delegados. Si siguió cómo funciona el manejo de eventos delegados, comprenderá que un controlador de eventos delegados debe compararse con muchos objetos muchas veces, por lo que elegir un selector tan eficiente como sea posible o agregar clases simples a sus objetos para que se puedan usar selectores más simples Aumentar el rendimiento del manejo de eventos delegados.
jfriend00
fuente
2
Increíble. Gracias por la descripción rápida y detallada. Eso tiene sentido que el tiempo de envío del evento aumentará al usar .on (), pero creo que todavía me cuesta decidir entre el tiempo de envío del evento aumentado frente al equipo de procesamiento de página inicial. En general, estoy a favor del tiempo de página inicial disminuido Por ejemplo, si tengo 200 elementos en una página (y más a medida que ocurren los eventos), es aproximadamente 100 veces más costoso en la carga inicial (ya que tiene que agregar 100 oyentes de eventos) en lugar de si agrego un oyente de eventos singular en el enlace
Buck
También quería decir que me convenció: no vincule todos los eventos a $ (documento). Enlace al padre que no cambia más cercano como sea posible. Supongo que aún tendré que decidir caso por caso cuando la delegación de eventos es mejor que vincular directamente un evento a un elemento. Y cuando necesito eventos vinculados al contenido dinámico (que no tiene un padre que no cambia), supongo que seguiré haciendo lo que @Vega recomienda a continuación y simplemente "vincularé los controladores después de insertar el contenido (el ) DOM ", usando el parámetro de contexto de jQuery en mi selector.
Buck
55
@ user1394692: si tiene muchos elementos casi idénticos, entonces puede tener sentido usar controladores de eventos delegados para ellos. Lo digo en mi respuesta. Si tuviera una tabla gigante de 500 filas y tuviera el mismo botón en cada fila de una columna en particular, usaría un controlador de eventos delegado en la tabla para servir todos los botones. Pero si tuviera 200 elementos y todos necesitaran su propio controlador de eventos único, instalar 200 controladores de eventos delegados no es más rápido que instalar 200 controladores de eventos directamente vinculados, sin embargo, el manejo de eventos delegados podría ser mucho más lento en la ejecución del evento.
jfriend00
Eres perfecto. ¡Totalmente de acuerdo! ¿Entiendo que esto es lo peor que puedo hacer $(document).on('click', '[myCustomAttr]', function () { ... });?
Artem Fitiskin
El uso de pjax / turbolinks plantea un problema interesante ya que todos los elementos se crean / eliminan dinámicamente (aparte de la primera carga de la página). Entonces, ¿es mejor vincular todos los eventos una vez al document, o vincular a selecciones más específicas page:loady desvincular estos eventos page:after-remove? Hmmm
Dom Christie
9

La delegación de eventos es una técnica para escribir sus controladores antes de que el elemento realmente exista en DOM. Este método tiene sus propias desventajas y debe usarse solo si tiene tales requisitos.

¿Cuándo deberías usar la delegación de eventos?

  1. Cuando vincula un controlador común para más elementos que necesitan la misma funcionalidad. (Ejemplo: desplazamiento de la fila de la tabla)
    • En el ejemplo, si tuviera que vincular todas las filas mediante la vinculación directa, terminaría creando un controlador n para n filas en esa tabla. Al usar el método de delegación, podría terminar manejando todos esos en 1 controlador simple.
  2. Cuando agrega contenido dinámico con más frecuencia en DOM (Ej: Agregar / eliminar filas de una tabla)

¿Por qué no deberías usar la delegación de eventos?

  1. La delegación de eventos es más lenta en comparación con la vinculación del evento directamente al elemento.
    • Compara el selector de objetivos en cada burbuja que golpea, la comparación será tan costosa como complicada.
  2. No hay control sobre el evento que burbujea hasta que golpea el elemento al que está vinculado.

PD: Incluso para contenido dinámico, no tiene que usar el método de delegación de eventos si está vinculando el controlador después de que el contenido se inserte en DOM. (Si el contenido dinámico se agrega, no se elimina / vuelve a agregar con frecuencia)

Selvakumar Arumugam
fuente
0

Me gustaría agregar algunos comentarios y argumentos en contra a la respuesta de jfriend00. (principalmente solo mis opiniones basadas en mi instinto)

No, NO debe vincular todos los controladores de eventos delegados al objeto del documento. Ese es probablemente el peor escenario que podría crear.

En primer lugar, la delegación de eventos no siempre acelera su código. En algunos casos, es ventajoso y en otros casos no. Debe usar la delegación de eventos cuando realmente necesite delegación de eventos y cuando se beneficie de ella. De lo contrario, debe vincular los controladores de eventos directamente a los objetos donde ocurre el evento, ya que esto generalmente será más eficiente.

Si bien es cierto que el rendimiento podría ser un poco mejor si solo va a registrarse y participar en un solo elemento, creo que no se compara con los beneficios de escalabilidad que brinda la delegación. También creo que los navegadores (van a estar) manejando esto de manera cada vez más eficiente, aunque no tengo pruebas de ello. ¡En mi opinión, la delegación de eventos es el camino a seguir!

En segundo lugar, NO debe vincular todos los eventos delegados a nivel de documento. Esta es exactamente la razón por la cual .live () fue desaprobado porque esto es muy ineficiente cuando tienes muchos eventos vinculados de esta manera. Para el manejo de eventos delegados, es MUCHO más eficiente vincularlos al elemento primario más cercano que no sea dinámico.

Estoy de acuerdo con esto. Si está 100% seguro de que un evento solo sucederá dentro de un contenedor, tiene sentido vincular el evento a este contenedor, pero aún argumentaría en contra de vincular eventos al elemento desencadenante directamente.

Tercero, no todos los eventos funcionan o todos los problemas pueden resolverse con delegación. Por ejemplo, si desea interceptar eventos clave en un control de entrada y bloquear el ingreso de claves no válidas en el control de entrada, no puede hacerlo con el manejo de eventos delegados porque para cuando el evento llegue al controlador delegado, ya ha procesado por el control de entrada y es demasiado tarde para influir en ese comportamiento.

Esto simplemente no es cierto. Consulte este código Pen: https://codepen.io/pwkip/pen/jObGmjq

document.addEventListener('keypress', (e) => {
    e.preventDefault();
});

Ilustra cómo puede evitar que un usuario escriba escribiendo el evento de pulsación de tecla en el documento.

Aquí hay momentos en que la delegación de eventos es obligatoria o ventajosa:

Cuando los objetos en los que está capturando eventos se crean / eliminan dinámicamente y aún desea capturar eventos en ellos sin tener que volver a vincular explícitamente los controladores de eventos cada vez que crea uno nuevo. Cuando tiene muchos objetos que todos quieren exactamente el mismo controlador de eventos (donde lotes es al menos cientos). En este caso, puede ser más eficiente en el momento de la configuración enlazar un controlador de eventos delegado en lugar de cientos o más controladores de eventos directos. Tenga en cuenta que el manejo de eventos delegados siempre es menos eficiente en tiempo de ejecución que los controladores de eventos directos.

Me gustaría responder con esta cita de https://ehsangazar.com/optimizing-javascript-event-listeners-for-performance-e28406ad406c

Event delegation promotes binding as few DOM event handlers as possible, since each event handler requires memory. For example, let’s say that we have an HTML unordered list we want to bind event handlers to. Instead of binding a click event handler for each list item (which may be hundreds for all we know), we bind one click handler to the parent unordered list itself.

Además, buscar en Google performance cost of event delegation googledevuelve más resultados a favor de la delegación de eventos.

Cuando intentas capturar (en un nivel superior de tu documento) los eventos que ocurren en cualquier elemento del documento. Cuando su diseño utiliza explícitamente el burbujeo de eventos y stopPropagation () para resolver algún problema o característica en su página. Para entender esto un poco más, uno necesita entender cómo funcionan los controladores de eventos delegados de jQuery. Cuando llamas a algo como esto:

$ ("# myParent"). on ('click', 'button.actionButton', myFn); Instala un controlador de eventos genérico jQuery en el objeto #myParent. Cuando un evento de clic aparece en este controlador de eventos delegado, jQuery tiene que revisar la lista de controladores de eventos delegados adjuntos a este objeto y ver si el elemento de origen del evento coincide con alguno de los selectores en los controladores de eventos delegados.

Debido a que los selectores pueden estar bastante involucrados, esto significa que jQuery tiene que analizar cada selector y luego compararlo con las características del objetivo del evento original para ver si coincide con cada selector. Esta no es una operación barata. No es gran cosa si solo hay uno de ellos, pero si pones todos tus selectores en el objeto del documento y hay cientos de selectores para comparar con cada evento burbujeado, esto puede comenzar a obstaculizar el rendimiento del manejo de eventos.

Por esta razón, desea configurar sus controladores de eventos delegados para que un controlador de eventos delegado esté tan cerca del objeto de destino como sea práctico. Esto significa que menos eventos aparecerán en cada controlador de eventos delegado, mejorando así el rendimiento. Poner todos los eventos delegados en el objeto del documento es el peor rendimiento posible porque todos los eventos burbujeados tienen que pasar por todos los controladores de eventos delegados y ser evaluados contra todos los posibles selectores de eventos delegados. Esto es exactamente por qué .live () está en desuso porque esto es lo que hizo .live () y resultó ser muy ineficiente.

¿Dónde se documenta esto? Si eso es cierto, entonces jQuery parece estar manejando la delegación de una manera muy ineficiente, y luego mis contraargumentos solo deberían aplicarse a Vanilla JS.

Aún así: me gustaría encontrar una fuente oficial que respalde esta afirmación.

:: EDITAR ::

Parece que jQuery está haciendo burbujas de eventos de una manera muy ineficiente (porque son compatibles con IE8) https://api.jquery.com/on/#event-performance

Entonces, la mayoría de mis argumentos aquí solo son válidos para Vanilla JS y los navegadores modernos.

Jules Colle
fuente