169 votes

Est-ce que tous les événements jQuery doivent être liés à $(document) ?

D'où vient cela

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

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

Après 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". Donc j'ai commencé à écrire du code de cette manière :

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

C'était également la façon recommandée de reproduire le comportement de l'événement .live() obsolète. Ce qui est important pour moi puisque beaucoup de mes sites ajoutent/suppriment dynamiquement des widgets tout le temps. Cependant, ce code ne se comporte pas exactement comme .live(), car seuls les éléments ajoutés au conteneur 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 ceci :

setTimeout(function() {
    $('body').append('Cliquer ne fait rien');
}, 1000);

Ce que je veux accomplir :

  1. le comportement ancien de .live() // signifiant attacher des événements à des éléments qui n'existent pas encore
  2. les avantages de .on()
  3. la performance la plus rapide pour lier des événements
  4. une manière simple de gérer les événements

J'attache désormais tous les événements de cette manière :

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

Cela 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 et le sélecteur secondaire n'est évalué que lorsque l'événement se produit (veuillez me corriger si je me trompe ici). L'espace de noms est génial car il rend plus facile le basculement du gestionnaire d'événements.

Ma solution/Question

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

Si vous avez lu tout cela, merci. J'apprécie tous les retours/connaissances.

Hypothèses :

  1. Utilisation de jQuery prenant 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/

1 votes

220voto

jfriend00 Points 152127

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 pourriez 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 d'autres non. Vous devriez utiliser la délégation d'événements lorsque vous en avez réellement besoin et lorsque vous en bénéficiez. Sinon, vous devriez 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 .live() a été obsolète car c'est très inefficace lorsque vous avez beaucoup d'événements liés de cette manière. Pour la gestion des événements déléguée, 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 et tous les problèmes ne peuvent pas être résolus avec la délégation. Par exemple, si vous voulez intercepter des événements de touches sur un contrôle de saisie et bloquer les touches invalides d'être entrées dans le contrôle de saisie, vous ne pouvez pas le faire avec la gestion des événements déléguée car lorsque l'événement remonte jusqu'au gestionnaire délégué, il a déjà été traité par le contrôle de saisie et il est trop tard pour influencer ce comportement.

Voici des 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 voulez 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 le même gestionnaire d'événements (où beaucoup signifie au moins des centaines). Dans ce cas, il peut être plus efficace au moment de la configuration 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ée est toujours moins efficace au moment de l'exécution que les gestionnaires d'événements directs.
  • Lorsque vous essayez de capturer (à un niveau plus élevé dans votre document) des événements qui se produisent sur n'importe quel élément du document.
  • Lorsque votre conception utilise explicitement la propagation d'événements et stopPropagation() pour résoudre un problème ou une fonctionnalité sur votre page.

Pour mieux comprendre cela, il est nécessaire de comprendre comment fonctionnent les gestionnaires d'événements délégués de jQuery. Lorsque vous appelez quelque chose comme ceci :

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

Cela installe un gestionnaire d'événements jQuery générique sur l'objet #myParent. Lorsqu'un événement de clic remonte vers 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.

Comme les sélecteurs peuvent être assez complexes, cela signifie que jQuery doit analyser chaque sélecteur et ensuite le comparer aux caractéristiques de la cible d'événement d'origine pour voir s'il correspond à chaque sélecteur. C'est une opération coûteuse. Ce n'est pas grave s'il n'y en a qu'un, mais si vous mettez tous vos sélecteurs sur l'objet document et qu'il y avait des centaines de sélecteurs à comparer pour chaque événement propagé, cela pourrait sérieusement entraver les performances du traitement des événements.

Pour cette raison, vous voulez configurer vos gestionnaires d'événements délégués de sorte qu'un gestionnaire d'événements délégué soit aussi proche de l'objet cible que possible. Cela signifie que moins d'événements passeront à travers chaque gestionnaire d'événements délégué, améliorant ainsi les performances. Mettre tous les événements délégués sur l'objet document est la pire performance possible car tous les événements propagés 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 .live() faisait et s'est avéré très inefficace.


Par conséquent, pour obtenir des performances optimisées :

  1. Utilisez uniquement la gestion des événements déléguée lorsqu'elle fournit effectivement une fonctionnalité dont vous avez besoin ou améliore les performances. Ne l'utilisez pas systématiquement car c'est facile lorsque vous n'en avez pas réellement besoin. Elle fonctionne en réalité moins bien au moment de la distribution des événements que la liaison directe des événements.
  2. Liez 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 voulez 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 compris comment fonctionne la gestion des événements délégués, vous comprendrez qu'un gestionnaire d'événements délégué doit être comparé à de nombreux objets un grand nombre de fois, alors choisir un sélecteur aussi efficace que possible ou ajouter des classes simples à vos objets pour que des sélecteurs plus simples puissent être utilisés augmentera les performances de la gestion des événements délégués.

2 votes

Génial. Merci pour la description rapide et détaillée. Il est logique que le temps de distribution de l'événement augmentera en utilisant .on(), mais je pense que j'hésite toujours entre le temps de distribution accru de l'événement et le traitement initial de la page. Je suis généralement en faveur d'un temps de page initial réduit. Par exemple, si j'ai 200 éléments sur une page (et plus au fur et à mesure des événements), c'est environ 100 fois plus coûteux lors du chargement initial (puisqu'il doit ajouter 100 écouteurs d'événements) plutôt que si j'ajoute un écouteur d'événements unique sur le conteneur parent lien

0 votes

Aussi, je voulais dire que tu m'as convaincu - Ne lie pas tous les événements à $(document). Lie-les au parent le plus proche qui ne change pas autant que possible. Je suppose que je devrai quand même décider au cas par cas quand la délégation d'événement est meilleure que lier directement 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 fixe) je suppose que je continuerai à faire ce que recommande @Vega 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.

5 votes

@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 le dis dans ma réponse. Si j'avais un tableau géant 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 le tableau pour tous les boutons. Mais si j'avais 200 éléments et qu'ils avaient tous besoin de leur propre gestionnaire d'événements unique, installer 200 gestionnaires d'événements délégués n'est pas plus rapide que d'installer 200 gestionnaires d'événements liés directement, mais la gestion des événements déléguée pourrait être beaucoup plus lente lors de l'exécution de l'événement.

9voto

Vega Points 44724

La délégation d'événement est une technique pour écrire vos gestionnaires avant que l'élément n'existe réellement dans le 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énement ?

  1. Lorsque vous liez un gestionnaire commun pour plusieurs éléments qui nécessitent la même fonctionnalité. (Ex : survol de lignes de tableau)
    • Dans l'exemple, si vous deviez lier toutes les lignes en utilisant une liaison directe, vous finiriez par créer n gestionnaire pour n lignes dans ce tableau. En utilisant la méthode de délégation, vous pourriez finir par gérer tous ces éléments avec un seul gestionnaire simple.
  2. Lorsque vous ajoutez du contenu dynamique plus fréquemment dans le DOM (Ex : Ajouter/supprimer des lignes d'un tableau)

Pourquoi ne pas utiliser la délégation d'événement ?

  1. La délégation d'événement est plus lente comparée à la liaison directe de l'événement à l'élément.
    • Elle compare le sélecteur cible à chaque bulle qu'elle atteint, la comparaison sera aussi coûteuse qu'elle est compliquée.
  2. Aucun contrôle sur la propagation de l'événement 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énement si vous liez le gestionnaire après l'insertion des contenus dans le DOM. (Si le contenu dynamique est ajouté pas fréquemment enlevé/réajouté)

3voto

Jules Colle Points 2482

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 devriez 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 pourriez 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 d'autres cas non. Vous devriez utiliser la délégation d'événements lorsque vous en avez réellement besoin et que vous en bénéficiez. Sinon, vous devriez lier directement les gestionnaires d'événements aux objets où l'événement se produit car cela sera généralement plus efficace.

Il est vrai que la performance pourrait être légèrement meilleure si vous ne devez enregistrer un événement que pour un seul élément, mais je crois que cela ne dépasse pas les avantages de la scalabilité apportée par la délégation. Je crois aussi que les navigateurs gèrent cela de manière de plus en plus efficace, bien que je n'ai pas de preuve de cela. À mon avis, la délégation d'événements est la voie à suivre !

Deuxièmement, vous ne devriez PAS lier tous les événements délégués au niveau du document. C'est précisément pourquoi .live() a été abandonné car c'est très inefficace lorsque vous avez beaucoup d'événements liés de cette manière. Pour la gestion des événements délégués, il est BEAUCOUP plus efficace de les lier au parent le plus proche qui n'est pas dynamique.

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

Troisièmement, tous les événements ne fonctionnent pas et tous les problèmes ne peuvent pas être résolus avec la délégation. Par exemple, si vous souhaitez intercepter des événements de touche sur un contrôle de saisie et bloquer les touches invalides d'être saisies dans le contrôle de saisie, vous ne pouvez pas le faire avec la gestion des événements délégués car au moment où l'événement remonte à l'agent délégué, il a déjà été traité par le contrôle de saisie 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();
});

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

Voici des moments où la délégation d'événements est nécessaire 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 à réenregistrer explicitement les gestionnaires d'événements à chaque fois que vous en créez un nouveau. Lorsque vous avez beaucoup d'objets qui veulent tous le même gestionnaire d'événements exact (où beaucoup est au moins des centaines). Dans ce cas, il peut être plus efficace au moment de la configuration de lier un gestionnaire d'événements délégué plutôt que des centaines ou plus de gestionnaires d'événements directs. Remarque, la gestion des événements délégués est toujours moins efficace à l'exécution que les gestionnaires d'événements directs.

J'aimerais répondre avec cette citation de https://ehsangazar.com/optimizing-javascript-event-listeners-for-performance-e28406ad406c

La délégation d'événements favorise la liaison du moins de gestionnaires d'événements DOM possible, car chaque gestionnaire d'événements nécessite de la mémoire. Par exemple, imaginons que nous avons une liste HTML non ordonnée à laquelle nous voulons lier des gestionnaires d'événements. Au lieu de lier un gestionnaire d'événements de clic pour chaque élément de liste (qui peut être des centaines pour tout ce que nous savons), nous lions un gestionnaire de clic à l'élément parent de la liste non ordonnée elle-même.

De plus, en cherchant sur coût de performance de la délégation d'événements google retourne 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 dans le document. Lorsque votre conception utilise explicitement la propagation d'événements et stopPropagation() pour résoudre un problème ou une fonctionnalité sur votre page. Pour mieux comprendre cela, il faut comprendre comment fonctionnent les gestionnaires d'événements délégués de jQuery. Lorsque vous appelez quelque chose comme cela :

$("#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 remonte à ce gestionnaire d'événements délégué, jQuery doit passer en revue 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.

Parce que les sélecteurs peuvent être assez complexes, cela signifie que jQuery doit analyser chaque sélecteur puis le comparer aux caractéristiques de la cible d'événement originale pour voir s'il correspond à chaque sélecteur. Ce n'est pas une opération bon marché. Ce n'est pas un gros problème s'il y en a seulement 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 remonté, cela peut sérieusement commencer à entraver la performance de la gestion des événements.

Pour cette raison, vous voulez configurer vos gestionnaires d'événements délégués de telle sorte qu'un gestionnaire d'événements délégué soit aussi proche de l'objet cible que possible. Cela signifie que moins d'événements remonteront à travers chaque gestionnaire d'événements délégué, améliorant ainsi la performance. Mettre tous les événements délégués sur l'objet document est la pire performance possible car tous les événements remontés 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 précisément pourquoi .live() est obsolète car c'est ce que .live() faisait et s'est avéré être très inefficace.

Où cela est-il 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 s'appliquer qu'à JavaScript vanilla.

Malgré tout : j'aimerais trouver une source officielle soutenant cette affirmation.

: ÉDITION ::

Il semble que jQuery gère en effet la propagation des é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 s'appliquent que pour JavaScript vanilla et les navigateurs modernes.

2voto

Reincha Points 21

Apparemment, la délégation d'événements est en fait recommandée maintenant, du moins pour le vanilla js.

https://gomakethings.com/why-event-delegation-is-a-better-way-to-listen-for-events-in-vanilla-js/

"Performances web # On a l'impression qu'écouter chaque clic dans le document serait mauvais pour les performances, mais c'est en fait plus performant que d'avoir une multitude d'écouteurs d'événements sur des éléments individuels."

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X