Orlok: Dialog-Box - Confirm

Beitrag lesen

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