Scriptease.js: Vergleiche mit typsicheren Operatoren

Sprachen  –  39 Kommentare

Zwei Variablen zu vergleichen ist eine der grundlegenden Aufgaben jeder Programmiersprache. Erst einmal scheint die Aufgabe trivial: Wenn beide Variablen den gleichen Wert enthalten, sind sie gleich, ansonsten ungleich. Auf den zweiten Blick entdeckt man jedoch einige Stolperfallen.

In statisch typisierten Sprachen wie C, C++, Java und C# fällt die Antwort leicht: Zwei Wertetypen sind dann gleich, wenn sie den gleichen Wert enthalten; zwei Referenztypen sind es, wenn beide Referenzen auf dasselbe Objekt verweisen. In beiden Fällen gilt, dass der Typ identisch sein muss, typungleiche Variablen lassen sich ohne explizite Konvertierung in der Regel nicht miteinander vergleichen.

Mehr Infos

Was steckt hinter Scriptease.js?

JavaScript erlernt sich deutlich leichter als die meisten anderen Programmiersprachen. Dennoch ist die Sprache nicht per se einfach, ganz im Gegenteil. Die Palette reicht dabei von exotischen und selten verwendeten Schlüsselwörtern bis hin zu unerklärlichem und scheinbar widersprüchlichem Verhalten. Die neue Kolumne Scriptease.js führt in die Feinheiten von JavaScript ein und vermittelt sowohl subtile Eigenarten der Sprache als auch bemerkenswerte Interna.

Wer eine derartige, sich von C ableitende Sprache gewöhnt ist, erwartet vielleicht das gleiche Verhalten auch in JavaScript: Immerhin sieht JavaScript-Code vertraut aus. Er besteht ebenfalls aus geschweiften Klammern und Semikolons. Doch der Eindruck täuscht: Zwar ähnelt die Syntax von JavaScript der von C-Sprachen, die Semantik unterscheidet sich jedoch deutlich.

Hinsichtlich des Typsystems ist einer der wesentlichen Unterschiede die Tatsache, dass JavaScript über ein vollständig dynamisches Typsystem verfügt. Das bedeutet, dass sich der Typ einer Variablen jederzeit ändern kann. Das lässt sich mit dem typeof-Operator auf einfache Weise ermitteln:

var value = 'Hallo Welt';
console.log(typeof value); // => string
value = 23;
console.log(typeof value); // => number

Beim Vergleich zweier Variablen prüft JavaScript ähnlich wie die C-Sprachen prinzipiell auf Wert- beziehungsweise Referenzgleichheit:

var foo = 23,
bar = 23;
console.log(foo == bar); // => true

JavaScript behandelt die Datentypen number und boolean dabei als Wertetypen, Objekte jedoch als Referenztypen. Da Arrays und Funktionen intern ebenfalls als Objekt implementiert sind, werden auch sie bei einem Vergleich auf Referenzgleichheit geprüft – allerdings nicht auf Wertegleichheit:

var foo = [ 23, 42 ],
bar = [ 23, 42 ];
console.log(foo == bar); // => false

Einen Sonderfall stellt der Datentyp string dar, der in JavaScript weder als Werte- noch als Referenztyp angesehen wird: Die variable Länge spricht zwar für einen Referenztypen, jedoch vergleicht der ==-Operator den tatsächlichen Wert. Da Zeichenketten in JavaScript zudem unveränderlich (immutable) sind, lässt sich die Art der Implementierung nicht auf programmatischem Weg ermitteln. Letztlich spielt sie auch keine Rolle: Fakt ist, dass sich Zeichenketten bei Vergleichen wie Wertetypen verhalten:

var foo = 'Hallo Welt!',
bar = 'Hallo Welt!';
console.log(foo == bar); // => true

Für C#-Entwickler sieht das vertraut aus: Dort sind Zeichenketten als Referenztyp implementiert. Die Klasse System.String überschreibt den ==-Operator jedoch derart, dass bei einem Vergleich auf den eigentlichen Wert geprüft wird. Auch Java bietet dieses Verhalten im Rahmen der string.equals-Methode.

Bislang scheint alles vertraut, doch der Teufel steckt im Detail: Der ==-Operator von JavaScript ist nämlich durch das dynamische Typsystem nicht typsicher. Das bedeutet, dass JavaScript bei unterschiedlichen Typen versucht, einen der beiden Typen derart zu konvertieren, dass ein Vergleich möglich wird. Innerhalb der beiden Wertetypen number und boolean gelten daher die Werte 0 und false als gleich:

console.log(0 == false); // => true

Das ermöglicht es in JavaScript unter anderem, numerische Ausdrücke als Bedingungen für Schleifen oder Abfragen zu verwenden, wie das in C und C++ geht. In JavaScript endet der Einfluss der impliziten Konvertierung jedoch nicht bei den Wertetypen. Stattdessen wird bei Bedarf ebenfalls zwischen Werte- und Referenztypen hin und her konvertiert. Das führt dazu, dass man beispielsweise eine Zahl und ihre Repräsentation als Zeichenkette von JavaScript als gleich ansieht:

console.log(23 == '23'); // => true
console.log(0 == '0'); // => true

Das führt über die Transitivität des Vergleichsoperators dazu, dass auch die Zeichenkette '0' gleichbedeutend mit dem Wahrheitswert false ist:

console.log(false == '0'); // => true

Darüber hinaus wird die leere Zeichenkette als false interpretiert – hier greift die Transitivität jedoch nicht vollständig: Zwar sind '0' und false auf der einen Seite und false und '' auf der anderen Seite gleichbedeutend, nicht aber '0' und '':

console.log('0' == ''); // => false

Das mag auf den ersten Blick logisch erscheinen, führt aber dazu, dass zwei durchaus verschiedene Ausdrücke gleichermaßen zu false konvertiert werden und daher unter Umständen doch als gleich gelten.

Ausgesprochen interessant ist das Verhalten von JavaScript bei den beiden Werten null und undefined, die für ein unbekanntes Objekt beziehungsweise eine nicht initialisierte Variable stehen: Sie werden im direkten Vergleich mit den Werten false, 0 und '' als verschieden angesehen, wie das folgende Codebeispiel zeigt:

console.log(false == null); // => false
console.log(false == undefined); // => false

Allerdings gelten sie gegenseitig als identisch:

console.log(undefined == null); // => true

Überraschenderweise werden beide dennoch als false interpretiert, wenn man sie als Bedingung im Rahmen einer Schleife oder einer Abfrage verwendet:

var foo = undefined;
if(foo) {
console.log('Diese Zeile wird nie ausgeführt ...');
} // => false