Sollten alle JQuery-Ereignisse an $ (Dokument) gebunden sein?

163

Woher kommt das?

Als ich jQuery zum ersten Mal lernte, habe ich normalerweise Ereignisse wie diese angehängt:

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

Nachdem ich mehr über die Auswahlgeschwindigkeit und die Ereignisdelegierung erfahren habe, habe ich an mehreren Stellen gelesen, dass "die jQuery-Ereignisdelegierung Ihren Code schneller macht". Also fing ich an, Code wie folgt zu schreiben:

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

Dies war auch die empfohlene Methode, um das Verhalten des veralteten .live () -Ereignisses zu replizieren. Das ist mir wichtig, da viele meiner Websites ständig Widgets dynamisch hinzufügen / entfernen. Das obige Verhalten verhält sich jedoch nicht genau wie .live (), da nur Elemente, die dem bereits vorhandenen Container '.my-widget' hinzugefügt wurden, das Verhalten erhalten. Wenn ich nach dem Ausführen dieses Codes dynamisch einen weiteren HTML-Block hinzufüge, erhalten diese Elemente die an sie gebundenen Ereignisse nicht. So was:

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


Was ich erreichen möchte:

  1. Das alte Verhalten von .live () // bedeutet, dass Ereignisse an noch nicht vorhandene Elemente angehängt werden
  2. die Vorteile von .on ()
  3. schnellste Leistung zum Binden von Ereignissen
  4. Einfache Möglichkeit, Ereignisse zu verwalten

Ich füge jetzt alle Ereignisse wie folgt hinzu:

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

Welches scheint alle meine Ziele zu erfüllen. (Ja, es ist aus irgendeinem Grund im IE langsamer, keine Ahnung warum?) Es ist schnell, da nur ein einzelnes Ereignis an ein einzelnes Element gebunden ist und der sekundäre Selektor nur ausgewertet wird, wenn das Ereignis eintritt (bitte korrigieren Sie mich, wenn dies hier falsch ist). Der Namespace ist fantastisch, da er das Umschalten des Ereignis-Listeners erleichtert.

Meine Lösung / Frage

Ich fange an zu denken, dass jQuery-Ereignisse immer an $ (document) gebunden sein sollten.
Gibt es einen Grund, warum Sie dies nicht tun möchten?
Könnte dies als bewährte Methode angesehen werden? Wenn nicht, warum?

Wenn Sie das Ganze gelesen haben, danke. Ich freue mich über alle Rückmeldungen und Erkenntnisse.

Annahmen:

  1. Verwenden von jQuery, das .on()// mindestens Version 1.7 unterstützt
  2. Sie möchten, dass das Ereignis zu dynamisch hinzugefügten Inhalten hinzugefügt wird

Lesungen / Beispiele:

  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/
Bock
quelle

Antworten:

215

Nein - Sie sollten NICHT alle delegierten Ereignishandler an das documentObjekt binden . Dies ist wahrscheinlich das Szenario mit der schlechtesten Leistung, das Sie erstellen können.

Zunächst einmal beschleunigt die Ereignisdelegierung Ihren Code nicht immer. In einigen Fällen ist dies vorteilhaft und in einigen Fällen nicht. Sie sollten die Ereignisdelegierung verwenden, wenn Sie tatsächlich eine Ereignisdelegierung benötigen und wenn Sie davon profitieren. Andernfalls sollten Sie Ereignishandler direkt an die Objekte binden, in denen das Ereignis auftritt, da dies im Allgemeinen effizienter ist.

Zweitens sollten Sie NICHT alle delegierten Ereignisse auf Dokumentebene binden. Dies ist genau der Grund, warum dies .live()veraltet war, da dies sehr ineffizient ist, wenn viele Ereignisse auf diese Weise gebunden sind. Für die Behandlung delegierter Ereignisse ist es VIEL effizienter, sie an das nächste übergeordnete Element zu binden, das nicht dynamisch ist.

Drittens funktionieren nicht alle Ereignisse oder alle Probleme können mit der Delegierung gelöst werden. Wenn Sie beispielsweise Schlüsselereignisse auf einem Eingabesteuerelement abfangen und die Eingabe ungültiger Schlüssel in das Eingabesteuerelement blockieren möchten, können Sie dies nicht mit der Behandlung delegierter Ereignisse tun, da dies bereits der Fall ist, wenn das Ereignis an den delegierten Handler weitergeleitet wird wurde von der Eingabesteuerung verarbeitet und es ist zu spät, um dieses Verhalten zu beeinflussen.

Hier sind Zeiten, in denen eine Ereignisdelegierung erforderlich oder vorteilhaft ist:

  • Wenn die Objekte, für die Sie Ereignisse erfassen, dynamisch erstellt / entfernt werden und Sie dennoch Ereignisse erfassen möchten, ohne die Ereignishandler jedes Mal, wenn Sie ein neues erstellen, explizit neu binden zu müssen.
  • Wenn Sie viele Objekte haben, die alle genau den gleichen Ereignishandler benötigen (wobei die Anzahl mindestens Hunderte beträgt). In diesem Fall kann es zur Einrichtungszeit effizienter sein, einen delegierten Ereignishandler zu binden, als Hunderte oder mehr direkte Ereignishandler. Beachten Sie, dass die Behandlung delegierter Ereignisse zur Laufzeit immer weniger effizient ist als die direkte Ereignisbehandlung.
  • Wenn Sie versuchen, Ereignisse (auf einer höheren Ebene in Ihrem Dokument) zu erfassen, die an einem beliebigen Element im Dokument auftreten.
  • Wenn Ihr Design explizit Ereignisblasen und stopPropagation () verwendet, um ein Problem oder eine Funktion auf Ihrer Seite zu lösen.

Um dies ein wenig besser zu verstehen, muss man verstehen, wie von jQuery delegierte Ereignishandler funktionieren. Wenn Sie so etwas nennen:

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

Es installiert einen generischen jQuery-Ereignishandler für das #myParentObjekt. Wenn ein Klickereignis zu diesem delegierten Ereignishandler sprudelt, muss jQuery die Liste der an dieses Objekt angehängten delegierten Ereignishandler durchgehen und prüfen, ob das Ursprungselement für das Ereignis mit einem der Selektoren in den delegierten Ereignishandlern übereinstimmt.

Da Selektoren ziemlich involviert sein können, bedeutet dies, dass jQuery jeden Selektor analysieren und dann mit den Merkmalen des ursprünglichen Ereignisziels vergleichen muss, um festzustellen, ob er mit jedem Selektor übereinstimmt. Dies ist keine billige Operation. Es ist keine große Sache, wenn es nur eine davon gibt. Wenn Sie jedoch alle Ihre Selektoren auf das Dokumentobjekt setzen und Hunderte von Selektoren mit jedem einzelnen Bubble-Ereignis zu vergleichen waren, kann dies die Leistung der Ereignisbehandlung ernsthaft beeinträchtigen.

Aus diesem Grund möchten Sie Ihre delegierten Ereignishandler so einrichten, dass sich ein delegierter Ereignishandler so nah wie möglich am Zielobjekt befindet. Dies bedeutet, dass weniger Ereignisse durch jeden delegierten Ereignishandler sprudeln und somit die Leistung verbessern. Das Einfügen aller delegierten Ereignisse in das Dokumentobjekt ist die schlechteste Leistung, da alle Bubble-Ereignisse alle delegierten Ereignishandler durchlaufen und gegen alle möglichen delegierten Ereignisselektoren ausgewertet werden müssen. Genau aus diesem Grund .live()wird dies abgelehnt, da dies .live()der Fall war und sich als sehr ineffizient erwiesen hat.


So erzielen Sie eine optimierte Leistung:

  1. Verwenden Sie die delegierte Ereignisbehandlung nur, wenn sie tatsächlich eine Funktion bietet, die Sie benötigen, oder die Leistung erhöht. Verwenden Sie es nicht immer, weil es einfach ist, denn wenn Sie es nicht wirklich brauchen. Zum Zeitpunkt des Ereignisversands ist die Leistung tatsächlich schlechter als bei der direkten Ereignisbindung.
  2. Fügen Sie delegierte Ereignishandler so weit wie möglich dem nächstgelegenen übergeordneten Element der Ereignisquelle hinzu. Wenn Sie die Behandlung delegierter Ereignisse verwenden, weil Sie dynamische Elemente haben, für die Sie Ereignisse erfassen möchten, wählen Sie das nächstgelegene übergeordnete Element aus, das selbst nicht dynamisch ist.
  3. Verwenden Sie einfach zu bewertende Selektoren für delegierte Ereignishandler. Wenn Sie die Funktionsweise der delegierten Ereignisbehandlung befolgt haben, werden Sie verstehen, dass ein delegierter Ereignishandler viele Male mit vielen Objekten verglichen werden muss, um einen möglichst effizienten Selektor auszuwählen oder Ihren Objekten einfache Klassen hinzuzufügen, damit einfachere Selektoren verwendet werden können Erhöhen Sie die Leistung der delegierten Ereignisbehandlung.
jfriend00
quelle
2
Genial. Vielen Dank für die schnelle und detaillierte Beschreibung. Das macht Sinn, dass sich die Ereignisversandzeit durch die Verwendung von .on () erhöht, aber ich denke, ich habe immer noch Schwierigkeiten, mich zwischen der erhöhten Ereignisversandzeit und dem anfänglichen Seitenverarbeitungsteam zu entscheiden. Ich bin in der Regel für verkürzte anfängliche Seitenzeit. Wenn ich beispielsweise 200 Elemente auf einer Seite habe (und mehr, wenn Ereignisse eintreten), ist dies beim ersten Laden etwa 100-mal teurer (da 100 Ereignis-Listener hinzugefügt werden müssen), als wenn ich dem Listen einen einzelnen Ereignis-Listener hinzufüge übergeordneter Container Link
Buck
Ich wollte auch sagen, dass Sie mich überzeugt haben - Binden Sie nicht alle Ereignisse an $ (Dokument). Binden Sie so nah wie möglich an den nächstgelegenen, sich nicht ändernden Elternteil. Ich denke, ich muss immer noch von Fall zu Fall entscheiden, wann die Ereignisdelegierung besser ist, als ein Ereignis direkt an ein Element zu binden. Und wenn ich Ereignisse benötige, die an dynamischen Inhalt gebunden sind (die kein sich nicht änderndes übergeordnetes Element haben), werde ich wahrscheinlich weiterhin das tun, was @Vega unten empfiehlt, und einfach die Handler binden, nachdem der Inhalt in (den) eingefügt wurde ) DOM "unter Verwendung des Kontextparameters von jQuery in meinem Selektor.
Buck
5
@ user1394692 - Wenn Sie viele nahezu identische Elemente haben, kann es sinnvoll sein, für sie delegierte Ereignishandler zu verwenden. Das sage ich in meiner Antwort. Wenn ich eine riesige Tabelle mit 500 Zeilen hätte und in jeder Zeile einer bestimmten Spalte dieselbe Schaltfläche hätte, würde ich einen delegierten Ereignishandler auf der Tabelle verwenden, um alle Schaltflächen bereitzustellen. Wenn ich jedoch 200 Elemente hätte und alle einen eigenen einzigartigen Ereignishandler benötigen, ist die Installation von 200 delegierten Ereignishandlern nicht schneller als die Installation von 200 direkt gebundenen Ereignishandlern. Die Behandlung delegierter Ereignisse kann jedoch bei der Ereignisausführung viel langsamer sein.
jfriend00
Du bist perfekt. Stimme voll und ganz zu! Verstehe ich, dass dies das Schlimmste ist, was ich tun kann $(document).on('click', '[myCustomAttr]', function () { ... });?
Artem Fitiskin
Die Verwendung von pjax / turbolinks stellt ein interessantes Problem dar, da alle Elemente dynamisch erstellt / entfernt werden (mit Ausnahme des Ladens der ersten Seite). So ist es besser , alle Ereignisse zu binden einmal das document, oder binden an spezifische Auswahl auf page:loadund unbind dieser Ereignisse auf page:after-remove? Hmmm
Dom Christie
9

Die Ereignisdelegierung ist eine Technik zum Schreiben Ihrer Handler, bevor das Element tatsächlich im DOM vorhanden ist. Diese Methode hat ihre eigenen Nachteile und sollte nur verwendet werden, wenn Sie solche Anforderungen haben.

Wann sollten Sie die Ereignisdelegierung verwenden?

  1. Wenn Sie einen gemeinsamen Handler für mehr Elemente binden, die dieselbe Funktionalität benötigen. (Beispiel: Tabellenzeile schweben)
    • Wenn Sie im Beispiel alle Zeilen mit direkter Bindung binden müssten, würden Sie am Ende einen n-Handler für n Zeilen in dieser Tabelle erstellen. Wenn Sie die Delegierungsmethode verwenden, können Sie alle in einem einfachen Handler verarbeiten.
  2. Wenn Sie dynamische Inhalte häufiger in DOM hinzufügen (Beispiel: Hinzufügen / Entfernen von Zeilen zu einer Tabelle)

Warum sollten Sie keine Ereignisdelegierung verwenden?

  1. Die Ereignisdelegierung ist langsamer als die direkte Bindung des Ereignisses an das Element.
    • Es vergleicht den Zielwähler bei jeder getroffenen Blase. Der Vergleich ist ebenso teuer wie kompliziert.
  2. Keine Kontrolle über das Sprudeln des Ereignisses, bis es auf das Element trifft, an das es gebunden ist.

PS: Selbst für dynamische Inhalte müssen Sie keine Ereignisdelegierungsmethode verwenden, wenn Sie den Handler binden, nachdem die Inhalte in DOM eingefügt wurden. (Wenn der dynamische Inhalt hinzugefügt wird, wird er nicht häufig entfernt / erneut hinzugefügt.)

Selvakumar Arumugam
quelle
0

Ich möchte der Antwort von jfriend00 einige Bemerkungen und Gegenargumente hinzufügen. (meistens nur meine Meinung basierend auf meinem Bauchgefühl)

Nein - Sie sollten NICHT alle delegierten Ereignishandler an das Dokumentobjekt binden. Dies ist wahrscheinlich das Szenario mit der schlechtesten Leistung, das Sie erstellen können.

Zunächst einmal beschleunigt die Ereignisdelegierung Ihren Code nicht immer. In einigen Fällen ist dies vorteilhaft und in einigen Fällen nicht. Sie sollten die Ereignisdelegierung verwenden, wenn Sie tatsächlich eine Ereignisdelegierung benötigen und wenn Sie davon profitieren. Andernfalls sollten Sie Ereignishandler direkt an die Objekte binden, in denen das Ereignis auftritt, da dies im Allgemeinen effizienter ist.

Zwar ist die Leistung möglicherweise etwas besser, wenn Sie sich nur für ein einzelnes Element registrieren und ein Ereignis durchführen. Ich glaube jedoch, dass dies nicht die Skalierbarkeitsvorteile der Delegierung beeinträchtigt. Ich glaube auch, dass Browser dies immer effizienter handhaben (werden), obwohl ich keinen Beweis dafür habe. Meiner Meinung nach ist Eventdelegation der richtige Weg!

Zweitens sollten Sie NICHT alle delegierten Ereignisse auf Dokumentebene binden. Genau aus diesem Grund wurde .live () nicht mehr unterstützt, da dies sehr ineffizient ist, wenn viele Ereignisse auf diese Weise gebunden sind. Für die Behandlung delegierter Ereignisse ist es VIEL effizienter, sie an das nächste übergeordnete Element zu binden, das nicht dynamisch ist.

Ich stimme dem irgendwie zu. Wenn Sie zu 100% sicher sind, dass ein Ereignis nur innerhalb eines Containers auftritt, ist es sinnvoll, das Ereignis an diesen Container zu binden, aber ich würde trotzdem dagegen sprechen, Ereignisse direkt an das auslösende Element zu binden.

Drittens funktionieren nicht alle Ereignisse oder alle Probleme können mit der Delegierung gelöst werden. Wenn Sie beispielsweise Schlüsselereignisse auf einem Eingabesteuerelement abfangen und die Eingabe ungültiger Schlüssel in das Eingabesteuerelement blockieren möchten, können Sie dies nicht mit der Behandlung delegierter Ereignisse tun, da dies bereits der Fall ist, wenn das Ereignis an den delegierten Handler weitergeleitet wird wurde von der Eingabesteuerung verarbeitet und es ist zu spät, um dieses Verhalten zu beeinflussen.

Das ist einfach nicht wahr. Bitte beachten Sie diesen CodePen: https://codepen.io/pwkip/pen/jObGmjq

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

Es zeigt, wie Sie verhindern können, dass ein Benutzer tippt, indem Sie das Tastendruckereignis im Dokument registrieren.

Hier sind Zeiten, in denen eine Ereignisdelegierung erforderlich oder vorteilhaft ist:

Wenn die Objekte, für die Sie Ereignisse erfassen, dynamisch erstellt / entfernt werden und Sie dennoch Ereignisse erfassen möchten, ohne die Ereignishandler jedes Mal, wenn Sie ein neues erstellen, explizit neu binden zu müssen. Wenn Sie viele Objekte haben, die alle genau den gleichen Ereignishandler benötigen (wobei die Anzahl mindestens Hunderte beträgt). In diesem Fall kann es zur Einrichtungszeit effizienter sein, einen delegierten Ereignishandler zu binden, als Hunderte oder mehr direkte Ereignishandler. Beachten Sie, dass die Behandlung delegierter Ereignisse zur Laufzeit immer weniger effizient ist als direkte Ereignishandler.

Ich möchte mit diesem Zitat von https://ehsangazar.com/optimizing-javascript-event-listeners-for-performance-e28406ad406c antworten

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.

Wenn Sie nach googeln suchen, erhalten Sie performance cost of event delegation googlemehr Ergebnisse zugunsten der Ereignisdelegierung.

Wenn Sie versuchen, Ereignisse (auf einer höheren Ebene in Ihrem Dokument) zu erfassen, die an einem beliebigen Element im Dokument auftreten. Wenn Ihr Design explizit Ereignisblasen und stopPropagation () verwendet, um ein Problem oder eine Funktion auf Ihrer Seite zu lösen. Um dies ein wenig besser zu verstehen, muss man verstehen, wie von jQuery delegierte Ereignishandler funktionieren. Wenn Sie so etwas nennen:

$ ("# myParent"). on ('click', 'button.actionButton', myFn); Es installiert einen generischen jQuery-Ereignishandler für das Objekt #myParent. Wenn ein Klickereignis zu diesem delegierten Ereignishandler sprudelt, muss jQuery die Liste der an dieses Objekt angehängten delegierten Ereignishandler durchgehen und prüfen, ob das Ursprungselement für das Ereignis mit einem der Selektoren in den delegierten Ereignishandlern übereinstimmt.

Da Selektoren ziemlich involviert sein können, bedeutet dies, dass jQuery jeden Selektor analysieren und dann mit den Merkmalen des ursprünglichen Ereignisziels vergleichen muss, um festzustellen, ob er mit jedem Selektor übereinstimmt. Dies ist keine billige Operation. Es ist keine große Sache, wenn es nur eine davon gibt. Wenn Sie jedoch alle Ihre Selektoren auf das Dokumentobjekt setzen und Hunderte von Selektoren mit jedem einzelnen Bubble-Ereignis zu vergleichen waren, kann dies die Leistung der Ereignisbehandlung ernsthaft beeinträchtigen.

Aus diesem Grund möchten Sie Ihre delegierten Ereignishandler so einrichten, dass sich ein delegierter Ereignishandler so nah wie möglich am Zielobjekt befindet. Dies bedeutet, dass weniger Ereignisse durch jeden delegierten Ereignishandler sprudeln und somit die Leistung verbessern. Das Platzieren aller delegierten Ereignisse auf dem Dokumentobjekt ist die schlechteste Leistung, da alle Bubble-Ereignisse alle delegierten Ereignishandler durchlaufen und gegen alle möglichen delegierten Ereignisselektoren ausgewertet werden müssen. Dies ist genau der Grund, warum .live () veraltet ist, weil .live () dies getan hat und sich als sehr ineffizient erwiesen hat.

Wo ist das dokumentiert? Wenn das stimmt, scheint jQuery die Delegierung sehr ineffizient zu behandeln, und dann sollten meine Gegenargumente nur auf Vanilla JS angewendet werden.

Dennoch: Ich würde gerne eine offizielle Quelle finden, die diese Behauptung stützt.

:: EDIT ::

Scheint, als würde jQuery tatsächlich Ereignisblasen auf sehr ineffiziente Weise ausführen (da sie IE8 unterstützen). Https://api.jquery.com/on/#event-performance

Die meisten meiner Argumente hier gelten also nur für Vanilla JS und moderne Browser.

Jules Colle
quelle