Tutti gli eventi jquery dovrebbero essere associati a $ (documento)?


164

Da dove viene questo

Quando ho appreso jQuery per la prima volta, di solito ho allegato eventi come questo:

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

Dopo aver appreso di più sulla velocità dei selettori e sulla delega degli eventi, ho letto in diversi punti che "la delega degli eventi jQuery renderà il tuo codice più veloce". Quindi ho iniziato a scrivere codice in questo modo:

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

Questo è stato anche il modo consigliato per replicare il comportamento dell'evento deprecato .live (). Il che è importante per me poiché molti dei miei siti aggiungono / rimuovono dinamicamente widget in ogni momento. Quanto sopra non si comporta esattamente come .live (), poiché solo gli elementi aggiunti al contenitore già esistente '.my-widget' otterranno il comportamento. Se aggiungo dinamicamente un altro blocco di HTML dopo l'esecuzione di quel codice, quegli elementi non otterranno gli eventi associati a loro. Come questo:

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


Cosa voglio ottenere:

  1. il vecchio comportamento di .live () // che significa collegare eventi a elementi non ancora esistenti
  2. i vantaggi di .on ()
  3. prestazioni più veloci per associare eventi
  4. Modo semplice per gestire gli eventi

Ora allego tutti gli eventi in questo modo:

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

Che sembra soddisfare tutti i miei obiettivi. (Sì, è più lento in IE per qualche motivo, non hai idea del perché?) È veloce perché solo un singolo evento è legato a un elemento singolare e il selettore secondario viene valutato solo quando si verifica l'evento (correggimi se qui è sbagliato). Lo spazio dei nomi è fantastico poiché semplifica l'attivazione / disattivazione del listener di eventi.

La mia soluzione / domanda

Quindi sto iniziando a pensare che gli eventi jQuery debbano essere sempre associati a $ (documento).
C'è qualche motivo per cui non vorresti farlo?
Potrebbe essere considerata una buona pratica? Se no, perché?

Se hai letto tutto questo, grazie. Apprezzo qualsiasi / tutti i feedback / approfondimenti.

ipotesi:

  1. Utilizzo di jQuery che supporta .on()// almeno la versione 1.7
  2. Si desidera che l'evento venga aggiunto al contenuto aggiunto dinamicamente

Letture / Esempi:

  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/

Risposte:


215

No: NON si devono associare tutti i gestori di eventi delegati documentall'oggetto. Questo è probabilmente lo scenario peggiore che potresti creare.

Innanzitutto, la delega degli eventi non rende sempre più veloce il codice. In alcuni casi, è vantaggioso e in alcuni casi no. È necessario utilizzare la delega degli eventi quando in realtà è necessaria la delega degli eventi e quando ne si beneficia. Altrimenti, è necessario associare i gestori di eventi direttamente agli oggetti in cui si verifica l'evento, in quanto ciò sarà generalmente più efficiente.

In secondo luogo, NON è necessario associare tutti gli eventi delegati a livello di documento. Questo è esattamente il motivo per cui è .live()stato deprecato perché questo è molto inefficiente quando ci sono molti eventi associati in questo modo. Per la gestione degli eventi delegati è MOLTO più efficiente associarli al genitore più vicino che non sia dinamico.

Terzo, non tutti gli eventi funzionano o tutti i problemi possono essere risolti con la delega. Ad esempio, se si desidera intercettare gli eventi chiave su un controllo di input e bloccare l'immissione di chiavi non valide nel controllo di input, non è possibile farlo con la gestione degli eventi delegati perché, al momento in cui l'evento passa al gestore delegato, ha già è stato elaborato dal controllo di input ed è troppo tardi per influenzare quel comportamento.

Ecco i momenti in cui è richiesta o vantaggiosa la delega degli eventi:

  • Quando gli oggetti su cui si stanno acquisendo gli eventi vengono creati / rimossi in modo dinamico e si desidera ancora catturare gli eventi su di essi senza dover ricollegare esplicitamente i gestori di eventi ogni volta che si crea uno nuovo.
  • Quando hai molti oggetti che vogliono tutti esattamente lo stesso gestore di eventi (dove i lotti sono almeno centinaia). In questo caso, potrebbe essere più efficiente al momento dell'installazione associare un gestore eventi delegato anziché centinaia o più gestori eventi diretti. Si noti che la gestione degli eventi delegati è sempre meno efficiente in fase di esecuzione rispetto ai gestori di eventi diretti.
  • Quando si tenta di acquisire (a un livello superiore nel documento) eventi che si verificano su qualsiasi elemento del documento.
  • Quando il tuo progetto utilizza esplicitamente il bubbling di eventi e stopPropagation () per risolvere alcuni problemi o funzionalità nella tua pagina.

Per capirlo un po 'di più, è necessario capire come funzionano i gestori di eventi delegati jQuery. Quando chiami qualcosa del genere:

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

Installa un gestore di eventi jQuery generico #myParentsull'oggetto. Quando un evento click arriva a questo gestore di eventi delegato, jQuery deve scorrere l'elenco dei gestori di eventi delegati collegati a questo oggetto e vedere se l'elemento di origine per l'evento corrisponde a uno dei selettori nei gestori di eventi delegati.

Poiché i selettori possono essere coinvolti in modo equo, ciò significa che jQuery deve analizzare ciascun selettore e quindi confrontarlo con le caratteristiche del target dell'evento originale per vedere se corrisponde a ciascun selettore. Questa non è un'operazione economica. Non è un grosso problema se ne esiste solo uno, ma se si posizionano tutti i selettori sull'oggetto documento e ci sono centinaia di selettori da confrontare con ogni singolo evento gorgogliante, questo può iniziare seriamente a ostacolare le prestazioni di gestione degli eventi.

Per questo motivo, si desidera impostare i gestori di eventi delegati in modo che un gestore di eventi delegato sia il più vicino possibile all'oggetto di destinazione. Ciò significa che un numero inferiore di eventi attraverserà ogni gestore di eventi delegato, migliorando così le prestazioni. Inserire tutti gli eventi delegati sull'oggetto documento è la peggiore prestazione possibile perché tutti gli eventi gorgogliati devono passare attraverso tutti i gestori di eventi delegati e essere valutati rispetto a tutti i possibili selettori di eventi delegati. Questo è esattamente il motivo per cui .live()è deprecato perché è quello che ha .live()fatto e si è rivelato molto inefficiente.


Quindi, per ottenere prestazioni ottimizzate:

  1. Utilizzare la gestione degli eventi delegati solo quando fornisce effettivamente una funzionalità necessaria o aumenta le prestazioni. Non solo usarlo sempre perché è facile perché quando non ne hai davvero bisogno. Al momento dell'invio degli eventi ha prestazioni peggiori rispetto all'associazione diretta agli eventi.
  2. Collegare i gestori di eventi delegati al genitore più vicino alla fonte dell'evento possibile. Se si utilizza la gestione degli eventi delegati perché sono presenti elementi dinamici per cui si desidera acquisire eventi, selezionare il genitore più vicino che non è esso stesso dinamico.
  3. Utilizzare selettori facili da valutare per i gestori di eventi delegati. Se hai seguito il modo in cui funziona la gestione degli eventi delegati, capirai che un gestore di eventi delegato deve essere confrontato con molti oggetti molte volte in modo da scegliere un selettore il più efficiente possibile o aggiungere classi semplici ai tuoi oggetti in modo da poter utilizzare selettori più semplici aumentare le prestazioni della gestione degli eventi delegati.

2
Eccezionale. Grazie per la descrizione rapida e dettagliata. Ciò ha senso che il tempo di invio degli eventi aumenterà utilizzando .on () ma penso che sto ancora facendo fatica a decidere tra il tempo di spedizione degli eventi aumentato e il team di elaborazione della pagina iniziale. In genere sono per un tempo di pagina iniziale ridotto. Ad esempio, se ho 200 elementi in una pagina (e più quando si verificano eventi), è circa 100 volte più costoso al caricamento iniziale (poiché deve aggiungere 100 listener di eventi) piuttosto che se aggiungo un listener di eventi singolare sul link
Buck,

Volevo anche dire che mi hai convinto - Non vincolare tutti gli eventi a $ (documento). Associare il più vicino possibile genitore che non cambia. Immagino che dovrò ancora decidere caso per caso quando la delega degli eventi è meglio che associare direttamente un evento a un elemento. E quando avrò bisogno di eventi legati a contenuti dinamici (che non hanno un genitore che non cambia) suppongo che continuerò a fare ciò che @Vega raccomanda di seguito e semplicemente "vincolerò i gestori dopo che il contenuto è stato inserito (il ) DOM ", usando il parametro di contesto di jQuery nel mio selettore.
Buck,

5
@ user1394692 - se si dispone di molti elementi quasi identici, potrebbe essere logico utilizzare gestori di eventi delegati per essi. Lo dico nella mia risposta. Se avessi una tabella di 500 righe giganti e avessi lo stesso pulsante in ogni riga di una particolare colonna, utilizzerei un gestore di eventi delegato sulla tabella per servire tutti i pulsanti. Ma se avessi 200 elementi e tutti avessero bisogno del loro unico gestore di eventi, l'installazione di 200 gestori di eventi delegati non è più veloce dell'installazione di 200 gestori di eventi direttamente associati, ma la gestione degli eventi delegati potrebbe essere molto più lenta all'esecuzione degli eventi.
jfriend00,

Tu sei perfetto. Completamente d'accordo! Capisco che questa è la cosa peggiore che posso fare - $(document).on('click', '[myCustomAttr]', function () { ... });?
Artem Fitiskin,

L'uso di pjax / turbolink rappresenta un problema interessante poiché tutti gli elementi vengono creati / rimossi in modo dinamico (a parte il caricamento della prima pagina). Quindi è meglio associare tutti gli eventi una volta a document, o associare selezioni più specifiche su page:loade separare questi eventi su page:after-remove? Hmmm
Dom Christie,

9

La delega di eventi è una tecnica per scrivere i gestori prima che l'elemento effettivamente esista in DOM. Questo metodo ha i suoi svantaggi e dovrebbe essere utilizzato solo se si dispone di tali requisiti.

Quando utilizzare la delega degli eventi?

  1. Quando si associa un gestore comune per più elementi che richiedono la stessa funzionalità. (Es .: passaggio del mouse sulla riga della tabella)
    • Nell'esempio, se dovessi associare tutte le righe usando l'associazione diretta, finiresti per creare n handler per n righe in quella tabella. Usando il metodo di delega potresti finire per gestire tutti quelli in 1 semplice gestore.
  2. Quando aggiungi contenuti dinamici più frequentemente in DOM (ad es. Aggiungi / rimuovi righe da una tabella)

Perché non dovresti usare la delega degli eventi?

  1. La delega degli eventi è più lenta se confrontata con l'associazione dell'evento direttamente all'elemento.
    • Confronta il selettore bersaglio su ogni bolla che colpisce, il confronto sarà tanto costoso quanto complicato.
  2. Nessun controllo sull'evento gorgogliante finché non colpisce l'elemento a cui è associato.

PS: anche per i contenuti dinamici non è necessario utilizzare il metodo di delega degli eventi se si esegue il bind del gestore dopo che i contenuti sono stati inseriti nel DOM. (Se il contenuto dinamico viene aggiunto non frequentemente rimosso / aggiunto di nuovo)



0

Vorrei aggiungere alcune osservazioni e controargomenti alla risposta di jfriend00. (principalmente solo le mie opinioni basate sul mio intestino)

No: NON si devono associare tutti i gestori di eventi delegati all'oggetto documento. Questo è probabilmente lo scenario peggiore che potresti creare.

Innanzitutto, la delega degli eventi non rende sempre più veloce il codice. In alcuni casi, è vantaggioso e in alcuni casi no. È necessario utilizzare la delega degli eventi quando in realtà è necessaria la delega degli eventi e quando ne si beneficia. Altrimenti, è necessario associare i gestori di eventi direttamente agli oggetti in cui si verifica l'evento, in quanto ciò sarà generalmente più efficiente.

Sebbene sia vero che le prestazioni potrebbero essere leggermente migliori se si desidera registrarsi ed eventi solo per un singolo elemento, ritengo che non sia all'altezza dei vantaggi di scalabilità offerti dalla delega. Credo anche che i browser (lo saranno) gestiranno questo in modo sempre più efficiente, anche se non ne ho la prova. Secondo me, la delega degli eventi è la strada da percorrere!

In secondo luogo, NON è necessario associare tutti gli eventi delegati a livello di documento. Questo è esattamente il motivo per cui .live () è stato deprecato perché è molto inefficiente quando ci sono molti eventi associati in questo modo. Per la gestione degli eventi delegati è MOLTO più efficiente associarli al genitore più vicino che non sia dinamico.

Sono un po 'd'accordo su questo. Se sei sicuro al 100% che un evento accadrà solo all'interno di un contenitore, ha senso associare l'evento a questo contenitore, ma continuerei a contestare direttamente gli eventi vincolanti con l'elemento scatenante.

Terzo, non tutti gli eventi funzionano o tutti i problemi possono essere risolti con la delega. Ad esempio, se si desidera intercettare gli eventi chiave su un controllo di input e bloccare l'immissione di chiavi non valide nel controllo di input, non è possibile farlo con la gestione degli eventi delegati perché al momento in cui l'evento passa al gestore delegato, ha già è stato elaborato dal controllo di input ed è troppo tardi per influenzare quel comportamento.

Questo semplicemente non è vero. Si prega di vedere questo codicePen: https://codepen.io/pwkip/pen/jObGmjq

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

Illustra come impedire a un utente di digitare registrando l'evento keypress sul documento.

Ecco i momenti in cui è richiesta o vantaggiosa la delega degli eventi:

Quando gli oggetti su cui si stanno acquisendo gli eventi vengono creati / rimossi in modo dinamico e si desidera ancora catturare gli eventi su di essi senza dover ricollegare esplicitamente i gestori di eventi ogni volta che si crea uno nuovo. Quando hai molti oggetti che vogliono tutti esattamente lo stesso gestore di eventi (dove i lotti sono almeno centinaia). In questo caso, potrebbe essere più efficiente al momento dell'installazione associare un gestore eventi delegato anziché centinaia o più gestori eventi diretti. Si noti che la gestione degli eventi delegati è sempre meno efficiente in fase di esecuzione rispetto ai gestori di eventi diretti.

Vorrei rispondere con questa citazione da 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.

Inoltre, cercare su Google performance cost of event delegation googlepiù risultati a favore della delega degli eventi.

Quando si tenta di acquisire (a un livello superiore nel documento) eventi che si verificano su qualsiasi elemento del documento. Quando il tuo progetto utilizza esplicitamente il bubbling di eventi e stopPropagation () per risolvere alcuni problemi o funzionalità nella tua pagina. Per capirlo un po 'di più, è necessario capire come funzionano i gestori di eventi delegati jQuery. Quando chiami qualcosa del genere:

$ ("# myParent"). on ('click', 'button.actionButton', myFn); Installa un gestore di eventi jQuery generico sull'oggetto #myParent. Quando un evento click arriva a questo gestore di eventi delegato, jQuery deve scorrere l'elenco dei gestori di eventi delegati collegati a questo oggetto e vedere se l'elemento di origine per l'evento corrisponde a uno dei selettori nei gestori di eventi delegati.

Poiché i selettori possono essere coinvolti in modo equo, ciò significa che jQuery deve analizzare ciascun selettore e quindi confrontarlo con le caratteristiche del target dell'evento originale per vedere se corrisponde a ciascun selettore. Questa non è un'operazione economica. Non è un grosso problema se ne esiste solo uno, ma se si posizionano tutti i selettori sull'oggetto documento e ci sono centinaia di selettori da confrontare con ogni singolo evento gorgogliante, questo può iniziare seriamente a ostacolare le prestazioni di gestione degli eventi.

Per questo motivo, si desidera impostare i gestori di eventi delegati in modo che un gestore di eventi delegato sia il più vicino possibile all'oggetto di destinazione. Ciò significa che un numero inferiore di eventi attraverserà ogni gestore di eventi delegato, migliorando così le prestazioni. Inserire tutti gli eventi delegati sull'oggetto documento è la peggiore prestazione possibile perché tutti gli eventi gorgogliati devono passare attraverso tutti i gestori di eventi delegati e essere valutati rispetto a tutti i possibili selettori di eventi delegati. Questo è esattamente il motivo per cui .live () è deprecato perché è ciò che ha fatto .live () e si è rivelato molto inefficiente.

Dove è documentato? Se questo è vero, allora jQuery sembra gestire la delega in un modo molto inefficiente, e quindi i miei contro-argomenti dovrebbero essere applicati solo a JS vanilla.

Tuttavia: vorrei trovare una fonte ufficiale a sostegno di questa affermazione.

:: MODIFICARE ::

Sembra che jQuery stia effettivamente facendo gorgogliare eventi in modo molto inefficiente (perché supportano IE8) https://api.jquery.com/on/#event-performance

Quindi la maggior parte dei miei argomenti qui vale solo per JS vaniglia e browser moderni.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.