this in JavaScript verstehen
About 4 min reading time
Gerade als ich mit JavaScript losgelegt habe, habe ich versucht this
in meinem Code zu vermeiden. Ich sage nur undefined is not a function
. Zu beliebig wirkte das Verhalten, verglichen mit anderen Sprachen, die ich damals kannte.
Da ich immer mal wieder gefragt werde, wie das Ganze denn funktioniert, dachte ich mir, ich schreibe es hier mal zusammen.
Kontext
Im Gegensatz zu anderen Programmiersprachen, bei denen sich this
auf Objekte bezieht (also die jeweilige Instanz einer Klasse), bezieht sich this
in JavaScript auf den Kontext, in dem es aufgerufen wird. Wichtig ist zudem, wie es aufgerufen wird, da wir dadurch diesen Kontext verändern können.
Globaler Kontext
Fangen wir an mit dem globalen Kontext an. Im Browser ist dies die Window Instanz.
Wenn man in einem Browser die Developer Tools öffnet und dort in der Konsole console.log(this)
eingibt und Enter drückt, sieht man die aktuelle Instanz des Window Objekts für die aktuelle Seite.
Dieser Code gibt, wenn ihr ihn in Node.js ausführt, ein leeres Objekt {}
aus.
Innerhalb von Konstruktoren
Die nächste Möglichkeit, this
zu verwenden ist innerhalb eines Objekts. Wenn wir ein Objekt mithilfe einer Funktion und des new
Keywords erstellen, können wir hier mit this
Werte für das Objekt definieren.
function Person(age) {
this.age = age;
}
const john = new Person(24);
console.log(john); // Person {age: 24, constructor: Object}
Objekt Methoden
Wenn wir nun Objekte erstellen und in diesen Methoden definieren, dann macht this
, was viele erwarten: es referenziert das eigentliche Objekt. Um den Code übersichtlicher zu gestalten, verzichte ich auf die Konstruktor Funktion.
let george = {
age: 25,
whoAmI() {
console.log(this);
}
};
george.whoAmI(); // {age: 25, whoAmI: ƒ whoAmI()}
Somit kann ich jetzt also auch auf Werte innerhalb meines Objekts zurückgreifen.
const ringo = {
age: 26,
getAge() {
return this.age;
}
};
console.log(ringo); // Object { age: 26, getAge: getAge() }
console.log(ringo.getAge()); // 26
Wahrscheinlich geht es Euch bis hierhin so, wie mir: Das alles ist vertraut und klappt ähnlich wie bei Objekt-orientierten Programmiersprachen. Kniffliger wird es, wenn wir uns Funktionen ansehen.
Funktionen
Innerhalb von Funktionen verweist this
immer auf den globalen Kontext (im Browser also das Window).
function showContext() {
console.log(this);
}
showContext(); // Window
Das ist so weit in Ordnung, wenn ich aber jetzt eine Funktion, die this
nutzt innerhalb eines Objekts nutze, dann würde ich erwarten, dass sie auf den gerade gültigen Kontext (also das Objekt) verweist. Tut sie aber nicht.
function showContext() {
console.log(this);
}
const paul = {
show() {
showContext();
}
};
showContext(); // Window
paul.show(); // Window
Obwohl meine Funktion show
in einem Objekt liegt, zeigt showContext
mir das Window an. Hier straucheln Einsteiger und mir ging es da auch nicht besser. Der Grund hierfür ist, dass der Kontext von showContext
weiterhin das Window
ist und der Aufruf für den Kontext keine Rolle spielt.
Aber wie kann ich damit umgehen?
Nehmen wir ein zweites, ähnliches Beispiel:
const john = {
sing() {
console.log('Let is be, let it be');
},
singLater() {
setTimeout(function () {
console.log(this.sing());
}, 1);
}
};
john.singLater(); // 💥 Uncaught TypeError: this.sing is not a function
Dies funktioniert nicht, da innerhalb von setTimeout
this
an das Window gebunden ist und dort gibt es die Funktion sing
nicht. Eine schnelle Lösung ist es, this
in einer anderen Variable zu speichern:
const john = {
sing() {
console.log('Let is be, let it be');
},
singLater() {
const that = this;
setTimeout(function () {
that.sing();
}, 1);
}
};
john.singLater(); // 🎵 Let is be, let it be
Manche nennen diese Variable that
, andere nennen sie self
. Der Name ist hier recht egal, wichtig ist, dass der richtige Kontext hier gespeichert wird und dieser innerhalb von anderen Funktionen dann genutzt werden kann.
Seit ein paar Jahren geht dies aber auch etwas einfacher.
Arrow Funktionen
Arrow Funktionen wurden mit ES2015 eingeführt und ihr werdet sie sicher schon oft gesehen haben. Manche halten sie für cooler, andere haben sich gemerkt, dass man mit ihnen weniger Probleme mit this
hat, denn sie haben einen wichtigen Unterschied zu normalen Funktionen:
const george = {
sing() {
console.log('Let is be, let it be');
},
singLater() {
setTimeout(() => {
console.log(this.sing());
}, 1);
}
};
george.singLater(); // 🎵 Let is be, let it be
Innerhalb von Arrow Funktionen ist this
an den gleichen Kontext gebunden, wie außerhalb der Funktion. Ich selbst nutze Arrow Funktionen daher am liebsten auch nur, wenn ich mit this
arbeiten muss, denn:
- so kann ich klar ausdrücken, dass ich hier eine Funktion habe, bei der der Kontext wichtig ist, und
- Arrow Funktionen sind anonyme Funktionen und daher sind sie nicht so gut zu debuggen.
Event Listener
Innerhalb von Event Listenern ist this
an das Element gebunden, welches das Event ausgelöst hat:
const button = document.querySelector('button');
button.addEventListener('click', function () {
console.log(this); // button
});
Auch hier kann ich Arrow Funktionen nutzen. Wenn ich this
und das Element benötige, dann habe ich zwei Möglichkeiten:
- den Kontext vorher in einer Variable
that
oderself
speichern, oder - in der Arrow Funktion das jeweilige Event nutzen, da dieses das Element in
event.currentTarget
speichert.
HTML:
<button>play</button>
JavaScript
function Jukebox(element) {
return {
listenStart() {
element.addEventListener('click', (event) => {
console.log(event.currentTarget); // my element
this.play(); // 🎵 Let is be, let it be
});
},
play() {
console.log('Let is be, let it be');
}
};
}
const button = document.querySelector('button');
const box = new Jukebox(button);
box.listenStart();
Das war ein erster Überblick, wie this
in JavaScript funktioniert und wie es sich, abhängig vom Kontext verhält. Arrow Funktionen sind hier das Mittel der Wahl, um das Verhalten so zu verändern, wie ihr es gerade benötigt. Wenn ihr mehr Artikel wie diesen lesen möchtet, schreibt mir gerne auf Mastodon.