this in JavaScript verstehen

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.

Entwicklerkonsole zeigt den Wert von this auf dieser 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:

  1. so kann ich klar ausdrücken, dass ich hier eine Funktion habe, bei der der Kontext wichtig ist, und
  2. 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:

  1. den Kontext vorher in einer Variable that oder self speichern, oder
  2. 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.