JavaScript: „this“ and „that“

Wer als Java-Entwickler in die Welt von JavaScript eintaucht, stolpert ab und zu über Eigenheiten oder Konstrukte, die sonderbar erscheinen und deren Sinn sich nicht leicht erschließt. Dabei wiegt die oberflächliche syntaktische Ähnlichkeit der beiden Sprachen in trügerische Sicherheit – bis man sich an einer Besonderheit von JavaScript bildlich den Kopf stößt.

Eine dieser Besonderheiten ist die Verwendung einer Variable that statt einem this wie in folgendem zufälligen Beispiel aus github:

TG.Objects.Animation.Frame = function (inX, inY, inTime) {
  var that = this;

  that.x = inX;
  that.y = inY;
  that.t = inTime;
}

Quelle: PureMunky Canvas-RPG: Frame.js

Tatsächlich liefert eine schnelle Suche nach dem Ausdruck „var that = this“ über 50.000 Treffer für JavaScript bei github. Ein weiteres Beispiel aus dieser Fülle ist dieses:

$.fn.lazy = function(fn) {
  var that = this;
  setTimeout(function() {
    fn.apply(that, arguments);
  }, 0);
};

Quelle: dotfiles: snip-jquery-lazy.js

Was soll das? Warum sollte man eine Referenz auf ein Objekt, in dessen Kontext eine äußere Funktion aufgerufen wird (this), erst auf eine neue Variable (that) umbiegen, bevor man es verwendet?

Die Antwort auf dieses Rätsel ergibt sich aus drei Teilen, die zusammen die merkwürdige Variable „that“ ergeben:

  1. Wie JavaScript aus Funktionen Methoden macht,
  2. einen „Fehler“ im JavaScript-Standard und
  3. die stringente Verwendung von Konventionen.

1. Objektmethoden in JavaScript

In anderen Programmiersprachen als JavaScript werden die unterschiedlichen Konzepte der Funktionen, Methoden und Konstruktoren syntaktisch klar getrennt, und als eigene Sprachelemente angeboten. In JavaScript übernimmt die Funktion alle drei Rollen und es entscheidet sich in der Verwendung, welche Rolle gerade gemeint ist:

Deklariere ich eine Funktion und verwende ich sie unabhängig von einem Objekt, ist sie genau dies, eine Funktion:

function moin() { 
  return „Moin!“; 
}
moin(); // Moin!

Verwende ich die Funktion aber im Zusammenhang mit einem Objekt, wird sie zur Methode dieses Objekts und kann sich die Eigenschaften des Objekts zu Nutze machen:

function moin() {
  return "Moin, " + this.name + "!";
}

var obj = {name : "Steffen", moin : moin}; 
obj.moin(); // Moin, Steffen!

Dabei hilft mir die besondere Variable this um an das Objekt heranzukommen (es wird in diesem Zusammenhang auch Receiver genannt.)

Zum Konstruktor wird die Funktion hingegen, wenn ich sie mit dem Schlüsselwort „new“ aufrufe:

function Moin(name) {
  this.botschaft = "Moin, " + name + "!"; 
}

var einMoin = new Moin("Steffen");
einMoin.botschaft; // = "Moin, Steffen!" 

Hier wird beim Eintritt in die Funktion „Moin()“ ein neues, leeres Objekt erzeugt und an „this“ gebunden. Dieses Objekt kann dann in der Funktion gestaltet werden (this.botschaft = "Moin, " + name + "!";) und es wird am Ende als Rückgabewert der Funktion zurückgegeben sodass wir ein frisches Moin-Objekt als Wert von „var einMoin“ erhalten.

Das Schlüsselwort „this“ erlaubt uns also den Zugriff auf das gerade relevante Objekt, wenn wir uns in einer Funktion befinden.

2. Ein Fehler im Standard

Leider enthält der JavaScript Standard einen Denkfehler, der den Nutzen von „this“ deutlich schmälert: innere Funktionen (Funktionen die in innerhalb anderer Funktionen deklariert werden) erben leider nicht die Referenz auf das Objekt ihrer Elternfunktion sondern werden an das globale Objekt gebunden. Dies demonstriert das folgende Beispiel:

var extFunc = function() {
  var that = this;
  
  setTimeout(function() {
  	document.getElementById("output").innerHTML = "this.property: " + this.property + ", that.property:"+ that.property + ".";
  }, 1000);
};

var obj = { property : "OBJ", extFunc : extFunc};
obj.extFunc(); //this.property: undefined, that.property:OBJ. 

(Hier zum ausprobieren: https://jsfiddle.net/cg77zdok/5/)

this.property“ ist undefined, da this in der inneren, anonymen Funktion auf das globale Objekt zeigt, auf dem keine Eigenschaft „property“ definiert ist. Als Workaround muss erst eine Referenz auf das aktuelle Objekt im Scope der inneren Funktion gespeichert werden, damit es weiterhin zur Verfügung steht und die falsche Referenz auf „this“ ignoriert werden kann.

3. „that“ als Konvention für gepeicherte Objektreferenzen

Als Konvention hat sich „that“ als Name für diese Variable bei vielen JavaScript-Entwicklern eingebürgert. Der Vorteil ist, dass sich der Verwendungszweck der Variable sofort erkennen lässt. Der Nachteil ist aber, dass nicht erkenntlich ist, welches Objekt erwartet wird. Deshalb ziehe ich persönlich einen sprechenderen Namen vor: z.B. var car = this, wenn wir von this eine Instanz von Car() erwarten.

Der zweite Effekt der Konvention ist im ersten, einleitenden Beispiel zu erkennen: Es wird eine Variabel „that“ für „this“ eingeführt, die nicht nötig ist, da keine innere Funktion definiert wird, die „that“ verwendet. Das Code-Schnipsel ist also so recht sinnfrei, es sei denn, man führt die strikte Einhaltung von Konventionen oder die Möglichkeit einer späteren Einführung einer inneren Funktion ins Feld, was aber individueller Geschmack sein dürfte.

#s