Tous les événements jquery doivent-ils être liés à $ (document)?

164

D'où cela vient

Quand j'ai appris jQuery pour la première fois, j'attachais normalement des événements comme celui-ci:

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

Après en avoir appris davantage sur la vitesse du sélecteur et la délégation d'événements, j'ai lu à plusieurs endroits que «la délégation d'événements jQuery rendra votre code plus rapide». J'ai donc commencé à écrire du code comme celui-ci:

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

C'était également la méthode recommandée pour répliquer le comportement de l'événement obsolète .live (). Ce qui est important pour moi car beaucoup de mes sites ajoutent / suppriment dynamiquement des widgets tout le temps. Ce qui précède ne se comporte pas exactement comme .live (), puisque seuls les éléments ajoutés au conteneur déjà existant '.my-widget' obtiendront le comportement. Si j'ajoute dynamiquement un autre bloc de html après l'exécution de ce code, ces éléments ne recevront pas les événements qui leur sont liés. Comme ça:

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


Ce que je veux réaliser:

  1. l'ancien comportement de .live () // signifiant attacher des événements à des éléments qui n'existent pas encore
  2. les avantages de .on ()
  3. performances les plus rapides pour lier des événements
  4. Un moyen simple de gérer les événements

J'attache maintenant tous les événements comme celui-ci:

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

Ce qui semble répondre à tous mes objectifs. (Oui, c'est plus lent dans IE pour une raison quelconque, je ne sais pas pourquoi?) C'est rapide car un seul événement est lié à un élément singulier et le sélecteur secondaire n'est évalué que lorsque l'événement se produit (veuillez me corriger si cela ne va pas ici). L'espace de noms est génial car il facilite le basculement de l'écouteur d'événements.

Ma solution / question

Je commence donc à penser que les événements jQuery devraient toujours être liés à $ (document).
Y a-t-il une raison pour laquelle vous ne voudriez pas faire cela?
Cela pourrait-il être considéré comme une meilleure pratique? Si non, pourquoi?

Si vous avez lu tout cela, merci. J'apprécie tous / tous les commentaires / idées.

Hypothèses:

  1. Utilisation de jQuery qui prend en charge .on()// au moins la version 1.7
  2. Vous voulez que l'événement soit ajouté au contenu ajouté dynamiquement

Lectures / Exemples:

  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/
mâle
la source

Réponses:

215

Non - vous ne devez PAS lier tous les gestionnaires d'événements délégués à l' documentobjet. C'est probablement le scénario le moins performant que vous puissiez créer.

Tout d'abord, la délégation d'événements ne rend pas toujours votre code plus rapide. Dans certains cas, c'est avantageux et dans certains cas non. Vous devez utiliser la délégation d'événements lorsque vous avez réellement besoin d'une délégation d'événements et lorsque vous en bénéficiez. Sinon, vous devez lier les gestionnaires d'événements directement aux objets où l'événement se produit, car cela sera généralement plus efficace.

Deuxièmement, vous ne devez PAS lier tous les événements délégués au niveau du document. C'est exactement pourquoi a .live()été déconseillé car cela est très inefficace lorsque de nombreux événements sont liés de cette manière. Pour la gestion déléguée des événements, il est BEAUCOUP plus efficace de les lier au parent le plus proche qui n'est pas dynamique.

Troisièmement, tous les événements ne fonctionnent pas ou tous les problèmes peuvent être résolus par délégation. Par exemple, si vous souhaitez intercepter des événements clés sur un contrôle d'entrée et empêcher la saisie de clés non valides dans le contrôle d'entrée, vous ne pouvez pas le faire avec la gestion déléguée des événements car au moment où l'événement remonte jusqu'au gestionnaire délégué, il a déjà été traité par le contrôle d'entrée et il est trop tard pour influencer ce comportement.

Voici les moments où la délégation d'événements est requise ou avantageuse:

  • Lorsque les objets sur lesquels vous capturez des événements sont créés / supprimés dynamiquement et que vous souhaitez toujours capturer des événements sur eux sans avoir à relier explicitement les gestionnaires d'événements chaque fois que vous en créez un nouveau.
  • Lorsque vous avez beaucoup d'objets qui veulent tous exactement le même gestionnaire d'événements (où lots vaut au moins des centaines). Dans ce cas, il peut être plus efficace au moment de l'installation de lier un gestionnaire d'événements délégué plutôt que des centaines ou plus de gestionnaires d'événements directs. Notez que la gestion des événements délégués est toujours moins efficace au moment de l'exécution que les gestionnaires d'événements directs.
  • Lorsque vous essayez de capturer (à un niveau supérieur dans votre document) des événements qui se produisent sur n'importe quel élément du document.
  • Lorsque votre conception utilise explicitement le bullage d'événements et stopPropagation () pour résoudre un problème ou une fonctionnalité de votre page.

Pour comprendre un peu plus cela, il faut comprendre comment fonctionnent les gestionnaires d'événements délégués jQuery. Lorsque vous appelez quelque chose comme ça:

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

Il installe un gestionnaire d'événements jQuery générique sur l' #myParentobjet. Lorsqu'un événement de clic atteint ce gestionnaire d'événements délégué, jQuery doit parcourir la liste des gestionnaires d'événements délégués attachés à cet objet et voir si l'élément d'origine de l'événement correspond à l'un des sélecteurs dans les gestionnaires d'événements délégués.

Étant donné que les sélecteurs peuvent être assez impliqués, cela signifie que jQuery doit analyser chaque sélecteur, puis le comparer aux caractéristiques de la cible d'événement d'origine pour voir s'il correspond à chaque sélecteur. Ce n'est pas une opération bon marché. Ce n'est pas grave s'il n'y en a qu'un, mais si vous placez tous vos sélecteurs sur l'objet document et qu'il y avait des centaines de sélecteurs à comparer à chaque événement bouillonnant, cela peut sérieusement commencer à entraver les performances de gestion des événements.

Pour cette raison, vous souhaitez configurer vos gestionnaires d'événements délégués afin qu'un gestionnaire d'événements délégués soit aussi proche que possible de l'objet cible. Cela signifie que moins d'événements apparaîtront dans chaque gestionnaire d'événements délégué, améliorant ainsi les performances. Placer tous les événements délégués sur l'objet document est la pire performance possible, car tous les événements à bulles doivent passer par tous les gestionnaires d'événements délégués et être évalués par rapport à tous les sélecteurs d'événements délégués possibles. C'est exactement pourquoi .live()est obsolète parce que c'est ce qui a .live()fait et cela s'est avéré très inefficace.


Donc, pour obtenir des performances optimisées:

  1. N'utilisez la gestion déléguée des événements que lorsqu'elle fournit réellement une fonctionnalité dont vous avez besoin ou augmente les performances. Ne l'utilisez pas toujours parce que c'est facile parce que vous n'en avez pas vraiment besoin. Il fonctionne en fait moins bien au moment de la répartition des événements que la liaison directe des événements.
  2. Attachez autant que possible les gestionnaires d'événements délégués au parent le plus proche de la source de l'événement. Si vous utilisez la gestion des événements déléguée parce que vous avez des éléments dynamiques pour lesquels vous souhaitez capturer des événements, sélectionnez le parent le plus proche qui n'est pas lui-même dynamique.
  3. Utilisez des sélecteurs faciles à évaluer pour les gestionnaires d'événements délégués. Si vous avez suivi le fonctionnement de la gestion des événements délégués, vous comprendrez qu'un gestionnaire d'événements délégués doit être comparé à de nombreux objets de nombreuses fois, donc choisir un sélecteur aussi efficace que possible ou ajouter des classes simples à vos objets afin que des sélecteurs plus simples puissent être utilisés. augmenter les performances de la gestion déléguée des événements.
jfriend00
la source
2
Impressionnant. Merci pour la description rapide et détaillée. Cela a du sens que le temps d'envoi des événements augmentera en utilisant .on (), mais je pense que j'ai encore du mal à décider entre l'augmentation du temps d'expédition des événements et l'équipe de traitement de la page initiale. Je suis généralement pour une réduction du temps de page initial. Par exemple, si j'ai 200 éléments sur une page (et plus au fur et à mesure que les événements se produisent), c'est environ 100 fois plus cher au chargement initial (car il doit ajouter 100 écouteurs d'événements) plutôt que si j'ajoute un auditeur d'événements singulier sur le lien de
Buck
Je voulais également dire que vous m'avez convaincu - Ne liez pas tous les événements à $ (document). Liez-vous au parent inchangé le plus proche possible. Je suppose que je vais encore devoir décider au cas par cas quand la délégation d'événements est meilleure que la liaison directe d'un événement à un élément. Et quand j'ai besoin d'événements liés au contenu dynamique (qui n'ont pas de parent qui ne change pas), je suppose que je vais continuer à faire ce que @Vega recommande ci-dessous et simplement "lier le (s) gestionnaire (s) après l'insertion du contenu dans (le ) DOM ", en utilisant le paramètre de contexte de jQuery dans mon sélecteur.
Buck
5
@ user1394692 - si vous avez beaucoup d'éléments presque identiques, il peut être judicieux d'utiliser des gestionnaires d'événements délégués pour eux. Je dis cela dans ma réponse. Si j'avais une table géante de 500 lignes et que j'avais le même bouton dans chaque ligne d'une colonne particulière, j'utiliserais un gestionnaire d'événements délégué sur la table pour servir tous les boutons. Mais si j'avais 200 éléments et qu'ils avaient tous besoin de leur propre gestionnaire d'événements unique, l'installation de 200 gestionnaires d'événements délégués n'est pas plus rapide que l'installation de 200 gestionnaires d'événements directement liés, mais la gestion des événements délégués pourrait être beaucoup plus lente lors de l'exécution des événements.
jfriend00
Tu est parfait. Entièrement d'accord! Dois-je comprendre que c'est la pire chose que je puisse faire - $(document).on('click', '[myCustomAttr]', function () { ... });?
Artem Fitiskin
L'utilisation de pjax / turbolinks pose un problème intéressant car tous les éléments sont créés / supprimés dynamiquement (à l'exception du chargement de la première page). Est-il donc préférable de lier tous les événements une fois à la document, ou de lier à des sélections plus spécifiques sur page:loadet de dissocier ces événements sur page:after-remove? Hmmm
Dom Christie
9

La délégation d'événements est une technique pour écrire vos gestionnaires avant que l'élément n'existe réellement dans DOM. Cette méthode a ses propres inconvénients et ne doit être utilisée que si vous avez de telles exigences.

Quand devriez-vous utiliser la délégation d'événements?

  1. Lorsque vous liez un gestionnaire commun pour plusieurs éléments nécessitant les mêmes fonctionnalités. (Ex: survol d'une ligne de tableau)
    • Dans l'exemple, si vous deviez lier toutes les lignes à l'aide de la liaison directe, vous finiriez par créer n gestionnaire pour n lignes de cette table. En utilisant la méthode de délégation, vous pourriez finir par gérer tous ceux-ci dans un seul gestionnaire simple.
  2. Lorsque vous ajoutez des contenus dynamiques plus fréquemment dans DOM (Ex: ajouter / supprimer des lignes d'une table)

Pourquoi vous ne devriez pas utiliser la délégation d'événements?

  1. La délégation d'événement est plus lente que la liaison de l'événement directement à l'élément.
    • Il compare le sélecteur de cible sur chaque bulle qu'il frappe, la comparaison sera aussi coûteuse que compliquée.
  2. Aucun contrôle sur l'événement bouillonnant jusqu'à ce qu'il atteigne l'élément auquel il est lié.

PS: Même pour les contenus dynamiques, vous n'avez pas besoin d'utiliser la méthode de délégation d'événements si vous liez le gestionnaire après que le contenu est inséré dans DOM. (Si le contenu dynamique n'est pas souvent ajouté / supprimé)

Selvakumar Arumugam
la source
0

Je voudrais ajouter quelques remarques et contre-arguments à la réponse de jfriend00. (principalement juste mes opinions basées sur mon instinct)

Non - vous ne devez PAS lier tous les gestionnaires d'événements délégués à l'objet document. C'est probablement le scénario le moins performant que vous puissiez créer.

Tout d'abord, la délégation d'événements ne rend pas toujours votre code plus rapide. Dans certains cas, c'est avantageux et dans certains cas non. Vous devez utiliser la délégation d'événements lorsque vous avez réellement besoin d'une délégation d'événements et lorsque vous en bénéficiez. Sinon, vous devez lier les gestionnaires d'événements directement aux objets où l'événement se produit, car cela sera généralement plus efficace.

Bien qu'il soit vrai que les performances pourraient être légèrement meilleures si vous ne vous enregistrez et n'organisez un événement que pour un seul élément, je pense que cela ne compense pas les avantages d'évolutivité que la délégation apporte. Je pense également que les navigateurs vont (vont) gérer cela de plus en plus efficacement, même si je n'en ai aucune preuve. À mon avis, la délégation d'événements est la voie à suivre!

Deuxièmement, vous ne devez PAS lier tous les événements délégués au niveau du document. C'est exactement pourquoi .live () est obsolète car c'est très inefficace lorsque de nombreux événements sont liés de cette façon. Pour la gestion déléguée des événements, il est BEAUCOUP plus efficace de les lier au parent le plus proche qui n'est pas dynamique.

Je suis plutôt d'accord là-dessus. Si vous êtes sûr à 100% qu'un événement ne se produira qu'à l'intérieur d'un conteneur, il est logique de lier l'événement à ce conteneur, mais je serais toujours contre la liaison d'événements à l'élément déclencheur directement.

Troisièmement, tous les événements ne fonctionnent pas ou tous les problèmes peuvent être résolus par délégation. Par exemple, si vous souhaitez intercepter des événements clés sur un contrôle d'entrée et empêcher la saisie de clés non valides dans le contrôle d'entrée, vous ne pouvez pas le faire avec la gestion des événements déléguée, car au moment où l'événement remonte au gestionnaire délégué, il a déjà été traité par le contrôle d'entrée et il est trop tard pour influencer ce comportement.

Ce n'est tout simplement pas vrai. Veuillez consulter ce codePen: https://codepen.io/pwkip/pen/jObGmjq

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

Il illustre comment vous pouvez empêcher un utilisateur de taper en enregistrant l'événement keypress sur le document.

Voici les moments où la délégation d'événements est requise ou avantageuse:

Lorsque les objets sur lesquels vous capturez des événements sont créés / supprimés dynamiquement et que vous souhaitez toujours capturer des événements sur eux sans avoir à relier explicitement les gestionnaires d'événements chaque fois que vous en créez un nouveau. Lorsque vous avez beaucoup d'objets qui veulent tous exactement le même gestionnaire d'événements (où lots vaut au moins des centaines). Dans ce cas, il peut être plus efficace au moment de l'installation de lier un gestionnaire d'événements délégué plutôt que des centaines ou plus de gestionnaires d'événements directs. Notez que la gestion des événements délégués est toujours moins efficace au moment de l'exécution que les gestionnaires d'événements directs.

Je voudrais répondre avec cette citation 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.

De plus, rechercher sur Google performance cost of event delegation googlerenvoie plus de résultats en faveur de la délégation d'événements.

Lorsque vous essayez de capturer (à un niveau supérieur dans votre document) des événements qui se produisent sur n'importe quel élément du document. Lorsque votre conception utilise explicitement le bullage d'événements et stopPropagation () pour résoudre un problème ou une fonctionnalité de votre page. Pour comprendre un peu plus cela, il faut comprendre comment fonctionnent les gestionnaires d'événements délégués jQuery. Lorsque vous appelez quelque chose comme ça:

$ ("# myParent"). on ('click', 'button.actionButton', myFn); Il installe un gestionnaire d'événements jQuery générique sur l'objet #myParent. Lorsqu'un événement de clic atteint ce gestionnaire d'événements délégué, jQuery doit parcourir la liste des gestionnaires d'événements délégués attachés à cet objet et voir si l'élément d'origine de l'événement correspond à l'un des sélecteurs dans les gestionnaires d'événements délégués.

Étant donné que les sélecteurs peuvent être assez impliqués, cela signifie que jQuery doit analyser chaque sélecteur, puis le comparer aux caractéristiques de la cible d'événement d'origine pour voir s'il correspond à chaque sélecteur. Ce n'est pas une opération bon marché. Ce n'est pas grave s'il n'y en a qu'un, mais si vous placez tous vos sélecteurs sur l'objet document et qu'il y avait des centaines de sélecteurs à comparer à chaque événement à bulles, cela peut sérieusement commencer à entraver les performances de gestion des événements.

Pour cette raison, vous souhaitez configurer vos gestionnaires d'événements délégués afin qu'un gestionnaire d'événements délégués soit aussi proche que possible de l'objet cible. Cela signifie que moins d'événements apparaîtront dans chaque gestionnaire d'événements délégué, améliorant ainsi les performances. Placer tous les événements délégués sur l'objet document est la pire performance possible, car tous les événements à bulles doivent passer par tous les gestionnaires d'événements délégués et être évalués par rapport à tous les sélecteurs d'événements délégués possibles. C'est exactement pourquoi .live () est obsolète car c'est ce que faisait .live () et il s'est avéré très inefficace.

Où est-ce documenté? Si c'est vrai, alors jQuery semble gérer la délégation de manière très inefficace, et mes contre-arguments ne devraient être appliqués qu'à vanilla JS.

Pourtant: j'aimerais trouver une source officielle soutenant cette affirmation.

:: ÉDITER ::

On dirait que jQuery fait effectivement des bulles d'événements de manière très inefficace (car ils prennent en charge IE8) https://api.jquery.com/on/#event-performance

Donc, la plupart de mes arguments ici ne sont valables que pour JS vanilla et les navigateurs modernes.

Jules Colle
la source