Hallo Linuchs
Danke für deine sehr ausführliche Darlegung.
Offenbar nicht ausführlich genug. – Aber daran lässt sich arbeiten. ;-)
Aber ich habe schon den addEventListener nicht verstanden.
Verstanden hast du sicher, dass alle Elemente eines HTML-Dokumentes, inklusive ihrer Attribute und Inhalte, beim Parsen in eine entsprechende Repräsentation des DOM übersetzt werden. Das heißt, sie werden in einer Objektstruktur abgebildet, auf die du über entsprechende Schnittstellen mittels JavaScript zugreifen kannst.
Bestimmte auf diese Weise von der Ausführungsumgebung erzeugte Objekte werden nun automatisch mit der Methode addEventListener
ausgestattet, und zwar alle HTML-Elemente sowie zum Beispiel auch das globale Objekt window
. Oder auch das Objekt document
, welches eine Eigenschaft des globalen Objektes ist und welches das jeweilige HTML-Dokument repräsentiert.
Wenn du nun diese Methode auf einem entsprechenden Objekt aufrufst, dann sind mindestens zwei Argumente zu übergeben, nämlich zuerst ein String, der den Namen des Ereignisses enthält welches du überwachen willst, sowie als zweites eine Funktion – oder eine Referenz auf eine solche, die dann aufgerufen werden soll, wenn das Ereignis schließlich eintritt – den Eventhandler.
<body>
<button type="button">Push</button>
<script>
var button = document.body.firstElementChild;
button.addEventListener('click', handler);
function handler (event) {
console.log(this === event.currentTarget); // true
};
</script>
</body>
In diesem Beispiel haben wir drei HTML-Elemente notiert, nämlich body
und dessen zwei Kindelemente button
und script
. Innerhalb des Scripts haben wir dann zunächst das button
-Element referenziert und einer gleichnamigen Variable als Wert zugewiesen. Dabei haben wir uns den Umstand zu Nutze gemacht, dass das Objekt, welches das body
-Element repräsentiert, in der Eigenschaft mit demselben Namen des Objektes document
hinterlegt ist. Da unser button
-Element zudem das erste Kindelement von body
ist, kann es hier über die Eigenschaft firstElementChild
von body
referenziert werden.
Die nächste Anweisung besteht nun darin, einen Event-Listener für das button
-Objekt zu registrieren.
Wir referenzieren dieses also zunächst über den Bezeichner der zuvor definierte Variable und rufen dann die Methode addEventListener
auf dem Objekt auf, wobei wir den String 'click'
als erstes Argument übergeben und eine Referenz auf die nachfolgend definierte Funktion mit dem Bezeichner handler
als zweites Argument. Damit haben wir dann die Funktion handler
als Eventhandler registriert für den Button und das Ereignis click
. Wenn nun auf den Button geklickt wird, dann wird die Funktion handler
automatisch aufgerufen.
That’s it!
Beziehungsweise noch nicht ganz. ;-) Denn wir sollten noch einen Blick auf die Funktion handler
werfen, für die nämlich ein Parameter mit dem (willkürlichen) Bezeichner event
deklariert wurde.
Wie ich in meinem ersten Posting schon erwähnte, wird beim Aufruf einer als Eventhandler registrierten Funktion automatisch das Objekt als Argument übergeben, welches das Ereignis repräsentiert – das sogenannte Event-Objekt – über dessen Eigenschaften verschiedene Informationen über das entsprechende Ereignis abrufbar sind. Das heißt, wenn die Funktion handler
aus dem Beispiel bei einem Klick auf den Button aufgerufen wird, dann wird der deklarierte Parameter event
beim Funktionsaufruf automatisch mit diesem Objekt initialisiert.
Die Eigenschaft currentTarget
des Event-Objektes enthält nun immer eine Referenz auf das Objekt, für welches der aufgerufene Eventhandler registriert wurde, ebenso wie die Funktionsvariable this
, weshalb in unserem Beispiel als Ergebnis des Vergleichs der Wert true
in die Konsole geschrieben wird.
Nun wandeln wir das Ursprungsbeispiel einmal etwas ab:
<body>
<button type="button">Push</button>
<script>
document.body.addEventListener('click', function (event) {
console.log(event.target.tagName); // BODY oder BUTTON
console.log(event.currentTarget.tagName); // BODY
});
</script>
</body>
Hier haben wir das selbe HTML, nur ein etwas anderes Script. Statt die Methode addEventListener
für das Objekt zu registrieren, welches das button
-Element repräsentiert, registrieren wir ihn hier direkt für body
. Außerdem referenzieren wir keine anderswo definierte Funktion sondern übergeben der Methode bei ihrem Aufruf direkt ein Funktionsobjekt, wobei für die Funktion wieder ein Parameter event
deklariert wird.
Im Funktionskörper dieser anonymen Funktion ist nun zunächst die Anweisung enthalten, den Wert der Eigenschaft tagName
des Objektes in die Konsole zu schreiben, welches in der Eigenschaft target
des beim Aufruf übergebenen Event-Objektes hinterlegt ist. Darüber hinaus ist die Anweisung enthalten, das gleiche mit dem Wert der Eigenschaft currentTarget
des Event-Objektes zu machen.
Da currentTarget
immer auf das Objekt verweist, für welches der aufgerufene Eventhandler registriert wurde, wird aufgrund dieser zweiten Anweisung bei jedem Klick irgendwo innerhalb von body
entsprechend der String BODY
in die Konsole geschrieben. Was davor ausgegeben wird, hängt jedoch davon ab, wohin geklickt wurde!
Denn der Wert der Eigenschaft target
zeigt immer auf das Objekt, bei dem das Ereignis tatsächlich eingetreten ist. Wird nun also auf den Button geklickt, so ist das Objekt welches das button
-Element repräsentiert als Wert dieser Eigenschaft hinterlegt, wenn hingegen auf body
geklickt wird, so wird dieses Objekt referenziert. Entsprechend wird also, je nach dem wohin geklickt wurde, zuerst entweder BODY
oder BUTTON
ausgegeben.
Darüber hinaus ist außerdem zu beachten, dass wenn nun auf den Button geklickt wird, schließlich sowohl BUTTON
als auch BODY
in die Konsole geschrieben wird, denn das Ereignis tritt nicht nur isoliert beim Zielobjekt auf, sondern wandert durch die Baumstruktur des DOM.
Sprich, es passiert zunächst in absteigender Richtung das globale Objekt window
, das Objekt document
, dann das in der Eigenschaft documentElement
von document
hinterlegte Objekt, welches das HTML-Element repräsentiert – und dann dessen Kindelement body
. Das ist die Capturing-Phase. Diese wird gefolgt von der Target-Phase, die eintritt, wenn das Ereignis schließlich beim Zielobjekt button
angekommen ist. Wenn das Event-Objekt also bis button
durchgereicht wurde, dann wird der Ereignispfad umgedreht und es beginnt die sogenannte Bubbling-Phase, das heißt, das Ereignis propagiert auf dem selben Weg wieder hoch bis zum globalen Objekt und passiert auf dem Weg dieselben Objekte.
Da zu diesen Objekten nun auch das Objekt zählt, welches das body
-Element repräsentiert und für das wir in dem Beispiel einen Event-Listener registriert haben, wird dieser entsprechend aufgerufen. Gäbe es also keine Bubbling-Phase, würde die Handlerfunktion in diesem Beispiel bei einem Klick auf den Button überhaupt nicht aufgerufen werden.
Was du nun als unwillkommene Nebenwirkung dieser Reise des Ereignisses durch das DOM empfunden hast, nämlich das dein Eventhandler aufgerufen wird obwohl das Ereignis bei einem Kindelement eingetreten ist, ist eigentlich eine unglaublich praktische Sache!
Denn da man wie gesehen das Zielobjekt, bei dem das Ereignis aufgetreten ist, über die Eigenschaft target
des Event-Objektes referenzieren kann, ist es überhaupt nicht nötig, die Verbindung des Eventhandlers mit dem überwachten Objekt explizit durch Registrierung herzustellen. Sondern statt dessen kann man den Eventhandler bei einem gemeinsamen Elternelement registrieren und dann innerhalb der Funktion selektieren. → Event Delegation
So spart man sich viel Schreibarbeit! ;-)
Das heißt, alles was du tun musst ist, Kriterien zu finden, nach denen du die potentiellen Zielelemente innerhalb der Handlerfunktion selektieren kannst …
list.addEventListener('click', function (event) {
let item = event.target;
if (item.className === 'menuLevel2') {
// do something
}
});
Viele Grüße,
Orlok
„Das Wesentliche einer Kerze ist nicht das Wachs, das seine Spuren hinterlässt, sondern das Licht.“ Antoine de Saint-Exupéry