Hallo Camping_RIDER
Warum funktioniert das
document.querySelector('#ctrl-open-my-dialog').addEventListener('click', event => { myDialogElement.showModal(); });
aber nicht das?
document.querySelector('#ctrl-open-my-dialog').addEventListener('click', myDialogElement.showModal);
Ich habe eine begründete Vermutung: Wegen
this
.
Die Vermutung ist richtig.
Oben wird eine Arrow Function verwendet.
Das spielt hier keine Rolle.
Entscheidend ist der Kontext, in dem die Methode showModal
ausgeführt wird. Im ersten Beispiel wird showModal
über einen Elementausdruck als Methode des Objektes ausgeführt, das in der Konstante mit dem Namen myDialogElement
hinterlegt ist. Das funktioniert, da bei dieser Form des Funktionsaufrufs die Kontextvariable this
auf das Objekt zeigt, über das die Funktion referenziert wurde. In diesem Fall ist das ein Dialog-Element und ein solches wird von der Methode auch erwartet.
Im zweiten Beispiel wird die Methode showModal
selbst als Eventhandler registriert. Sie wird über einen Elementausdruck referenziert, aber nicht aufgerufen. Alles was an die Methode addEventListener
übergeben wird, ist eine Referenz auf die Funktion. Die Information, über welches Objekt sie referenziert wurde, geht dabei verloren.
Bei dem Objekt, auf dem der Eventhandler in den Beispielen oben registriert wird, handelt es sich um einen Button und nicht um ein Dialog-Element. Wenn nun im zweiten Beispiel auf den Button geklickt wird, dann wird die Funktion showModal
als Eventhandler aufgerufen und dabei this
mit einer Referenz auf das Objekt initialisiert, auf dem der Handler registriert wurde, also mit einer Referenz auf das Button-Element.
Die Methode erwartet aber eine Referenz auf ein Dialog-Element.
Deswegen geht das schief.
Es ist hilfreich sich zu vergegenwärtigen, zu welchem Zeitpunkt eine Funktion an einen Kontext gebunden wird. Das passiert erst dann, wenn die Funktion aufgerufen wird.
// Just member expression → `this` is unbound
const method = object.method;
// Call expression → `this` is bound to window or undefined
method();
In dem Beispiel oben wird eine Methode über ein Objekt referenziert, aber nicht unmittelbar aufgerufen, sondern stattdessen in einer Konstante hinterlegt. Hier findet keinerlei Bindung an einen Kontext statt. Das Objekt wird lediglich dazu verwendet, den Eigenschaftswert zu ermitteln, in diesem Fall also die Funktionsreferenz. Nach der Auswertung des Ausdrucks wird die Referenz auf das Objekt weggeworfen.
Wird nun die Methode über die Konstante mit dem Namen method
referenziert, dann ist das im Grunde nichts anderes, als wenn man eine Funktion anspricht, die unabhängig von einem Objekt für sich alleine deklariert wurde. Bei dem folgenden Aufruf wird, wie bei jedem gewöhnlichen Funktionsaufruf, entweder window
oder undefined
als Wert für this
eingesetzt – abhängig davon, in welchem Modus das Programm ausgeführt wird.
// Method call → `this` is bound to object
const result = object.method();
Anders verhält es sich, wenn es sich bei dem Ausdruck wie in diesem Beispiel um einen Methodenaufruf handelt, die Funktion also nicht nur über ein Objekt referenziert, sondern auch gleich aufgerufen wird. In diesem Fall zeigt this
auf das Objekt, als dessen Eigenschaft die Funktion angesprochen wurde.
Oben war die begründete Vermutung, jetzt kommt Spekulation.
Ich glaube - wenn ich es richtig verstanden habe - du kannst den unteren Ansatz folgendermaßen retten:
document.querySelector('#ctrl-open-my-dialog').addEventListener('click', myDialogElement.showModal.bind(this));
Zumindest nach meinem Verständnis müsste bei dieser Variante der Kontext für
this
derselbe sein wie für die Arrow Function.
Bei dieser Variante hätte this
zwar tatsächlich denselben Wert wie innerhalb der Pfeilfunktion des ersten Beispiels, aber das ist leider nicht der Wert den wir brauchen. Die Registrierung der Eventhandler erfolgt hier im globalen Scope. Das bedeutet, this
enthält hier eine Referenz auf das globale Objekt window
. An diesen Wert bindest du nun die Methode showModal
. Das ergibt offensichtlich keinen Sinn. Dein Vorschlag ist allerdings schon nahe dran.
document.querySelector('#ctrl-open-my-dialog').addEventListener('click', myDialogElement.showModal.bind(myDialogElement));
Wenn du statt this
eine Referenz auf das Dialog-Element an die Methode bind
übergibst, dann klappt es. In diesem Fall wird eine gebundene Funktion erzeugt, welche die Methode mit dem Dialog-Element verknüpft. Es spielt dann keine Rolle mehr, dass die gebundene Funktion als Eventhandler im Kontext des Objektes aufgerufen wird, auf dem der Handler registriert wurde: showModal
wird im Kontext von myDialogElement
aufgerufen.
Viele Grüße,
Orlok