Verlorener Objektbezug

this ist undefined, und der Aufruf verrät den Grund

Nicht die Funktion bestimmt ihr this, sondern die Stelle, an der sie aufgerufen wird. Wer eine Methode aus ihrem Objekt herauslöst, nimmt ihr genau diese Information weg.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Seit 1997 am Markt Kleine Gruppen Präsenz und Live-Online Zertifizierte Trainer
Worum es geht

Eine Methode überlebt den Weg in einen Rückruf nicht

Der Klassiker: Eine Klasse Warenkorb hat eine Methode aktualisieren, und irgendwo steht knopf.addEventListener('click', korb.aktualisieren). Übergeben wird dabei nur die Funktion, nicht das Objekt dazu. Beim Klick ruft der Browser sie mit dem Element als this auf, also mit dem Knopf. Der Zugriff auf this.artikel liefert undefined, und die nächste Zeile scheitert mit Cannot read properties of undefined.

Unangenehmer sind die Fälle ohne Fehlermeldung. Landet dieselbe Methode in einem setTimeout, ist this im Browser das globale Objekt, und zwar auch dann, wenn dein Modul strikt ist, denn den Rückruf ruft die Laufzeitumgebung auf. this.summe ist dann undefined, eine Addition darauf ergibt NaN, und eine Zuweisung wie this.summe = 0 legt stillschweigend eine Eigenschaft am globalen Objekt an. Der Fehler zeigt sich erst als falsche Zahl in der Anzeige, und zwar an einer Stelle, die mit dem Rückruf nichts zu tun hat.

Was das teuer macht, ist der übliche Reparaturweg. Es wird ein bind ergänzt, dann eine Pfeilfunktion darum gelegt, dann ein const self = this weiter oben, bis irgendwann alles wieder läuft, ohne dass jemand sagen könnte, welcher der drei Eingriffe geholfen hat. Beim nächsten Umbau beginnt dasselbe Spiel von vorn, weil die eigentliche Regel nie ausgesprochen wurde.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Symptom, Ursache, Lösung

Fünf Beobachtungen und der jeweilige Grund

Fast jeder this-Fehler gehört zu einem von fünf Mustern. Entscheidend ist dabei nicht die Fehlermeldung, sondern die Frage, wer die Funktion am Ende aufruft und mit welchem Objekt.

Symptom

TypeError: Cannot read properties of undefined, ausgerechnet in einer Methode, die in den Tests einwandfrei läuft.

Ursache

Die Methode wurde aus ihrem Objekt gelöst, etwa durch const { speichern } = store oder durch die Übergabe als Rückruf, und dann ohne Objekt aufgerufen. Klassenrümpfe sind immer strikt, deshalb ist this dort undefined und nicht das globale Objekt.

Lösung

Im Konstruktor binden, die Methode als Klassenfeld mit Pfeilfunktion schreiben, oder an der Aufrufstelle eine Pfeilfunktion darum legen: () => store.speichern().

Symptom

Im Klick-Rückruf zeigt this auf das Knopf-Element statt auf die eigene Komponente, ohne dass ein Fehler erscheint.

Ursache

addEventListener ruft einen normalen Funktionsrückruf mit currentTarget als this auf. Der Zugriff auf eigene Eigenschaften geht damit ins Leere, ohne dass etwas abstürzt.

Lösung

Eine Pfeilfunktion registrieren, die die eigentliche Methode aufruft, oder ein Objekt mit handleEvent übergeben. Auf das auslösende Element kommst du weiterhin über event.currentTarget.

Symptom

In einem setTimeout-Rückruf ist this im Browser das window-Objekt, unter Node dagegen ein Timeout-Objekt.

Ursache

Den Rückruf ruft die Laufzeitumgebung auf, und beide setzen this nach ihren eigenen Regeln. Der strikte Modus deines Moduls ändert daran nichts, weil der Aufruf nicht in deinem Code passiert.

Lösung

Eine Pfeilfunktion als Rückruf verwenden, dann bleibt das this der umgebenden Methode erhalten. Auf this innerhalb eines Zeitgeber-Rückrufs solltest du dich grundsätzlich nicht verlassen.

Symptom

removeEventListener entfernt den Zuhörer nicht, der Rückruf läuft weiter.

Ursache

Beim Anmelden stand dort methode.bind(this) oder eine Pfeilfunktion. Beide erzeugen bei jedem Aufruf eine neue Funktion, und zum Entfernen zählt die Identität der Referenz.

Lösung

Die gebundene Funktion einmal in einem Feld ablegen und dieselbe Referenz zum Anmelden und Abmelden verwenden. Bequemer ist ein AbortController: Sein Signal beim Anmelden mitgeben und beim Aufräumen abort aufrufen, das entfernt auch mehrere Zuhörer auf einmal.

Symptom

Eine als Pfeilfunktion geschriebene Methode in einem Objektliteral findet über this nichts.

Ursache

Eine Pfeilfunktion hat kein eigenes this und nimmt das der umgebenden Ebene. Im obersten Bereich eines ES-Moduls ist das undefined, in CommonJS module.exports, in einem klassischen Skript das globale Objekt. Das Objektliteral selbst ist nie gemeint, denn es bildet keinen eigenen this-Bereich.

Lösung

Methoden in der Kurzform schreiben, also aktualisieren() { ... } statt aktualisieren: () => { ... }. Pfeilfunktionen bleiben für Rückrufe innerhalb dieser Methoden.

Vier Regeln, in dieser Reihenfolge geprüft

  1. 01 Mit new aufgerufen? Dann ist this das frisch erzeugte Objekt.
  2. 02 Mit call, apply oder bind? Dann ist this das übergebene Objekt.
  3. 03 Als obj.methode() aufgerufen? Dann ist this genau dieses obj.
  4. 04 Einfach als funktion() aufgerufen? Im strikten Modus ist this undefined.
  5. 05 Pfeilfunktion? Dann greift keine dieser Regeln, this kommt von außen.
Was du mitnimmst

Was du danach an jeder Aufrufstelle sofort siehst

Die Regeln sind überschaubar, sie stehen nur an einer ungewohnten Stelle: nicht bei der Funktion, sondern beim Aufruf. Wer sich angewöhnt, beim Lesen einer Funktion zuerst nach der Aufrufstelle zu suchen, beantwortet die Frage nach this in wenigen Sekunden.

Die vier Regeln in ihrer Reihenfolge

Zuerst new, dann eine ausdrückliche Bindung über call, apply oder bind, dann der Aufruf als obj.methode(), zuletzt der Aufruf ohne alles. Der Modus wirkt an zwei Stellen: Ohne alles ist this im strikten Modus undefined und sonst das globale Objekt, und auch f.call(null) landet im nicht strikten Modus beim globalen Objekt, während ein primitiver Wert dort in ein Objekt verpackt wird.

Die Aufrufstelle lesen, nicht die Definition

Der Punkt vor dem Funktionsnamen ist das entscheidende Zeichen. Wird die Funktion vorher in eine Variable, in einen Parameter oder in eine Destrukturierung gelegt, ist der Punkt verschwunden und mit ihm der Bezug.

Pfeilfunktion als Rückruf, Kurzform als Methode

Rückrufe innerhalb einer Methode gehören als Pfeilfunktion geschrieben, dann stimmt this ohne Zutun. Methoden eines Objekts oder einer Klasse dagegen brauchen die normale Form, weil eine Pfeilfunktion dort das falsche this mitbringt.

bind einmal aufrufen und die Referenz behalten

Jeder Aufruf von bind erzeugt eine neue Funktion. Wer beim Registrieren bind schreibt und beim Entfernen noch einmal, entfernt nichts. Entweder speicherst du die gebundene Funktion, oder du meldest den Zuhörer über das Signal eines AbortController ab.

Klassenfelder mit Pfeilfunktion und ihr Preis

Ein Feld wie aktualisieren = () => {} ist pro Instanz gebunden und überlebt jede Weitergabe. Dafür liegt es nicht mehr auf dem Prototyp, existiert einmal je Instanz und lässt sich in Tests nicht mehr über den Prototyp ersetzen.

handleEvent statt bind

addEventListener nimmt statt einer Funktion auch ein Objekt mit einer Methode handleEvent entgegen. Dann ist this dieses Objekt, und das Abmelden funktioniert mit derselben Referenz, mit der du angemeldet hast.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt

Vier Regeln, in dieser Reihenfolge

Wird eine Funktion mit new aufgerufen, ist this das neu erzeugte Objekt, und die Funktion gibt es zurück, sofern sie nichts anderes zurückgibt. Wird sie mit call, apply oder als gebundene Funktion aufgerufen, ist this das dort übergebene Objekt. Wird sie als Methode aufgerufen, also mit einem Punkt davor, ist this das Objekt links vom Punkt. Und wenn keine dieser Regeln greift, entscheidet der Modus.

Genau diese vierte Regel sorgt für die Verwirrung. Im nicht strikten Modus ist this bei einem Aufruf ohne alles das globale Objekt, im strikten Modus undefined. Klassenrümpfe und ES-Module sind immer strikt, ohne dass du use strict schreiben musst. Deshalb bekommst du in einer Klasse ein undefined, während derselbe Code in einem alten Skript stillschweigend auf window zugreift und dort irgendetwas findet.

Wichtig an der Reihenfolge ist, dass das Objekt links vom Punkt die schwächste der drei ausdrücklichen Regeln ist. const f = obj.methode; f(); enthält nirgends mehr einen Punkt, und die Zuweisung nimmt keinen Bezug mit. Dasselbe gilt für die Destrukturierung, für die Übergabe als Argument und für das Ablegen in einem Array oder in einer Map.

Warum Rückruffunktionen den Bezug abstreifen

Bei einem Rückruf entscheidet immer der, der ihn aufruft. addEventListener ruft einen gewöhnlichen Funktionsrückruf mit currentTarget als this auf, also mit dem Element, an dem der Zuhörer hängt. setTimeout im Browser ruft ihn mit dem globalen Objekt auf, unter Node mit dem Timeout-Objekt. Ein Promise ruft seinen then-Rückruf ohne this auf, dort ist es also undefined, sobald der Code strikt ist.

Ein paar Schnittstellen bieten eine Abkürzung an, die kaum jemand kennt: map, forEach, filter, some und every nehmen als zweites Argument ein thisArg entgegen, das im Rückruf als this gesetzt wird. liste.forEach(this.pruefen, this) ist damit gleichwertig zu einer Pfeilfunktion, und JSON.stringify setzt im replacer this auf das gerade behandelte Objekt.

Die stabilste Regel im Alltag lautet trotzdem: Übergib nie eine Methode direkt, sondern immer eine Pfeilfunktion, die sie aufruft. Damit steht der Aufruf mit Punkt an der Stelle, an der er ausgeführt wird, und die Frage nach this stellt sich gar nicht erst. Der zweite Vorteil zeigt sich beim Lesen, denn () => korb.aktualisieren() sagt sofort, welches Objekt gemeint ist.

Pfeilfunktionen lösen das eine und schaffen das andere

Eine Pfeilfunktion hat kein eigenes this, kein arguments und keinen eigenen Bezug für super. Alles davon kommt aus dem umgebenden Bereich, und zwar zum Zeitpunkt der Definition. Deshalb funktioniert sie hervorragend als Rückruf innerhalb einer Methode und sehr schlecht als Methode selbst: In einem Objektliteral zeigt this dann auf die Modulebene, nicht auf das Objekt.

Als Konstruktor lässt sie sich nicht verwenden, ein new darauf wirft einen TypeError, und sie hat keinen prototype. Auch call und apply können ihr this nicht ändern, die Argumente werden schlicht ignoriert. Für Funktionen, die absichtlich mit wechselndem Kontext aufgerufen werden, etwa Prototyp-Methoden oder Ereignisrückrufe, die das Element brauchen, ist die Pfeilform deshalb die falsche Wahl.

Klassenfelder mit Pfeilfunktion sind der bequemste Ausweg für Rückrufe und haben einen Preis, den man kennen sollte. Das Feld gehört zur Instanz, nicht zum Prototyp: Bei tausend Instanzen existiert die Funktion tausendmal. Vererbung greift nicht mehr wie erwartet, weil eine Unterklasse das Feld überschreibt statt die Methode zu überlagern, und in Tests lässt sich nichts mehr über den Prototyp ersetzen. Für eine Handvoll Komponenten ist das gleichgültig, für eine Klasse mit vielen Instanzen nicht.

bind, call und apply im Detail

call und apply rufen die Funktion sofort auf und unterscheiden sich nur in der Übergabe der Argumente: call nimmt sie einzeln, apply als Array. Seit es die Spread-Schreibweise gibt, ist apply selten geworden, f.call(obj, ...args) leistet dasselbe. bind dagegen ruft nichts auf, sondern gibt eine neue Funktion zurück, deren this dauerhaft festliegt.

Dieses dauerhaft ist wörtlich zu nehmen. Eine gebundene Funktion lässt sich nicht erneut binden, ein zweites bind hat keine Wirkung mehr, und auch call kann ihr this nicht mehr ändern. Praktisch ist das für Rückrufe, hinderlich beim Testen. Ein Detail, das beim Suchen hilft: Der Name der gebundenen Funktion beginnt mit bound, im Stack steht also bound aktualisieren statt aktualisieren.

bind kann außerdem Argumente vorbelegen. f.bind(null, 'de') gibt eine Funktion zurück, die 'de' immer als erstes Argument mitbringt, alle weiteren werden angehängt. Für den reinen Zweck der Vorbelegung ist eine Pfeilfunktion meist lesbarer, und sie hat den Vorteil, dass ihre Absicht am Aufruf sichtbar bleibt.

this außerhalb von Methoden

Auf der obersten Ebene eines ES-Moduls ist this undefined, in einem CommonJS-Modul zeigt es auf module.exports, und in einem klassischen Skript ohne type="module" auf das globale Objekt. Wer eine Bibliothek schreibt, die in beiden Formaten läuft, sollte sich deshalb nie auf this außerhalb einer Funktion verlassen. Das portable globale Objekt heißt in allen Umgebungen globalThis.

In einer gewöhnlichen Funktion, die für sich allein aufgerufen wird, entscheidet der Modus: undefined im strikten, das globale Objekt im nicht strikten. Genau darauf beruht der alte Trick var self = this, den man noch in gewachsenem Code findet. Er ist nicht falsch, nur überflüssig, seit Pfeilfunktionen dasselbe leisten und dabei nicht vergessen werden können.

Der schnellste Weg zur Antwort im Zweifelsfall führt nicht über eine Konsolenausgabe, sondern über einen Haltepunkt in der ersten Zeile der Funktion. Im Bereich Scope steht, worauf this gerade zeigt, und im Aufrufstapel steht direkt darüber die Stelle, die den Aufruf ausgelöst hat. Diese beiden Angaben zusammen beantworten die Frage endgültig, während eine Ausgabe nur den Wert zeigt und nicht seinen Grund.

Dazu passende Kurse

Im JavaScript-Grundkurs bei cmt arbeitest du an kleinen Beispielen durch, woran sich der this-Bezug entscheidet .

TypeScript kennt einen eigenen this-Parameter, mit dem ein falscher Aufrufkontext schon beim Übersetzen auffällt, geübt wird das in Kurse, in denen Typen solche Fehler vorab melden .

Wissen prüfen

Wie sicher bist du beim Thema wirklich?

Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.

Ein sehr gutes, praxisorientiertes und nachhaltiges Seminar. So sollte es immer sein. Vielen Dank.
LimeSurvey - Anwendertraining Teil 1 (Grundlagen)
Die Schulung ist gerade für Einsteiger oder Entwickler*innen mit eingestaubtem Basiswissen sehr hilfreich.
Vue.js 3 Grundkurs
Ich bin sehr glücklich Franz als Trainer gehabt zu haben. Er hat offensichtlich unglaublich viel Wissen zu den Themen, und schafft es, dieses auch interaktiv und verständlich weiterzugeben.
React Komplettausbildung

Häufige Fragen

Wann brauche ich bind überhaupt noch, wenn es Pfeilfunktionen gibt?
Wenn du dieselbe Referenz mehrfach brauchst, etwa zum An- und Abmelden eines Zuhörers, oder wenn du eine bestehende Funktion mit festem Kontext weitergibst, ohne sie umschreiben zu dürfen. Für Rückrufe, die du an Ort und Stelle schreibst, ist die Pfeilfunktion der kürzere und deutlichere Weg.
Warum ist this in meiner Klasse undefined und nicht window?
Weil Klassenrümpfe immer im strikten Modus laufen, unabhängig davon, ob use strict irgendwo steht. Im strikten Modus ist this bei einem Aufruf ohne Objekt undefined. Das ist eine Verbesserung: Der Fehler zeigt sich sofort, statt sich als stillschweigender Zugriff auf globale Eigenschaften zu tarnen.
Kostet eine Pfeilfunktion als Klassenfeld etwas?
Ja, zwei Dinge. Die Funktion liegt auf jeder Instanz statt einmal auf dem Prototyp, bei vielen Instanzen ist das messbar. Und sie ist über den Prototyp nicht mehr erreichbar, was Vererbung und das Ersetzen in Tests erschwert. Bei wenigen Instanzen ist beides gleichgültig.
Wie prüfe ich schnell, worauf this in einer Funktion zeigt?
Setz einen Haltepunkt in die erste Zeile der Funktion und sieh im Bereich Scope nach. Der Aufrufstapel darüber zeigt zusätzlich, wer die Funktion aufgerufen hat, und genau das ist die eigentliche Antwort. Eine Konsolenausgabe zeigt nur den Wert und lässt die Ursache offen.
Persönlich für dich da

Deine Ansprechpartner

Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.

Yves Hoppe

Yves Hoppe

Weiterbildung & Beratung

Hilft dir, aus dem Webentwicklung-Programm den passenden Kurs für deinen Stand zu finden.

Norbert Jansen

Norbert Jansen

Beratung & Inhouse

Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.

Aufrufkontexte, die keine Überraschung mehr sind

In den JavaScript-Kursen bei cmt spielst du diese Fälle an lauffähigem Code durch, vom Ereignisrückruf bis zur Klasse mit vielen Instanzen.