zurück zum Artikel

Scriptease.js: Vergleiche mit typsicheren Operatoren

Golo Roden

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

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

Diesem Wirrwarr lĂ€sst sich auf zwei Arten begegnen: Entweder indem man ihm aus dem Weg geht oder indem man sich dieses spezielle Verhalten zu Nutze macht. Die erste Wahl lĂ€sst sich ausgesprochen leicht umsetzen: Beim Einsatz des ===- anstelle des ==-Operators wird zusĂ€tzlich zum eigentlichen Wert beziehungsweise der Referenz auch der jeweilige Typ berĂŒcksichtigt, und JavaScript verhĂ€lt sich genauso wie die C-Sprachen:

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

Analog zum !=-Operator gibt es den !==-Operator, der auf Ungleichheit unter BerĂŒcksichtigung des Typs prĂŒft:

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

Als Faustregel lĂ€sst sich festhalten, dass man stets die typsicheren Operatoren === und !== verwenden sollte, um durch die implizite Konvertierung hervorgerufene und daher gut versteckte logische Fehler zu verhindern. Dennoch gibt es Szenarien, in denen die implizite Konvertierung gewĂŒnscht sein kann. HĂ€ufig ist beispielsweise bei Zeichenketten zu prĂŒfen, ob sie einen Wert enthalten, bevor eine Verarbeitung angestoßen wird. WĂ€hrend in C# Konstrukte wie

if(foo != null && foo.Length > 0) { ... }

oder

if(String.IsNullOrWhitespace(foo)) { ... }

erforderlich sind, genĂŒgt es in JavaScript, die zu prĂŒfende Zeichenkette als Bedingung anzugeben:

if(foo) { ... }

Auch die Existenz von Objekteigenschaften lĂ€sst sich auf dem Weg vornehmen. Da JavaScript beim Zugriff auf eine nicht vorhandene Eigenschaft den Wert undefined zurĂŒckliefert, lĂ€sst sich auf einfache Weise deren Existenz prĂŒfen und entsprechend handeln:

if(customer.middleName) { ... }

Zu guter Letzt machen sich auch die beiden logischen Operatoren || und && das Verhalten des Typsystems zu Nutze. Anders als ihre Pendants in den C-Sprachen geben sie nĂ€mlich keinen Wahrheitswert zurĂŒck, sondern einen der beiden zu verknĂŒpfenden Werte. Der ODER-Operator || liefert im Fall, dass sich der erste Operand zu true evaluieren lĂ€sst, diesen zurĂŒck, ansonsten den zweiten:

var a = 23,
b = 42,
result = a || b;

Die Variable result enthĂ€lt in dem Fall den Wert 23. Beim Verwenden dieses Ausdruck in einer Bedingung ergibt sich genau der gewĂŒnschte Effekt: Die Bedingung wird zu true evaluiert, da 23 ungleich 0 gilt und nur die Zahl 0 zu false ausgewertet wird.

Als Nebeneffekt lĂ€sst sich der ||-Operator aber auch fĂŒr einen anderen Einsatzzweck verwenden: den Fallback auf einen Standardwert. Beispielsweise könnte man beim Auslesen von Konfigurationsdaten zunĂ€chst den Wert des Benutzers nutzen. Falls dieser jedoch nicht gesetzt wird, verwendet man einen geeigneten Standardwert:

var userValue = getUserValue(),
value = userValue || 23;

Analog funktioniert auch der &&-Operator, mit der Ausnahme, dass sich dessen Logik umgekehrt verhĂ€lt: LĂ€sst sich der erste Operand zu true evaluieren, gibt er den zweiten Operanden zurĂŒck, ansonsten den ersten.

Das Typsystem von JavaScript wirkt auf den ersten Blick unscheinbar und harmlos, birgt jedoch etliche Besonderheiten. FĂŒr einen Entwickler, der eine andere Sprache gewöhnt ist, stellen diese durchaus eine große Gefahr dar, schließlich ist es ein Leichtes, versehentlich nur schwer auffindbare logische Fehler einzubauen. Wer sich jedoch auf JavaScript und dessen Besonderheiten einlĂ€sst, wird mit ausgesprochen mĂ€chtigen Sprachfunktionen belohnt, die in vielen FĂ€llen einiges an Tipparbeit sparen können. Das fĂŒhrt zu kompakterem, besser lesbarem und letztlich zu verstĂ€ndlicherem Code.

Als Zusammenfassung kann gelten, dass Vergleiche prinzipiell ausschließlich mit typsicheren Operatoren === und !== ausgefĂŒhrt werden sollten. Dennoch hat die implizite Konvertierung ihre Daseinsberechtigung und lĂ€sst sich insbesondere zur eleganten PrĂŒfung der Existenz von Objekten und fĂŒr den kompakten Fallback auf Standardwerte nutzen.

Golo Roden [1]
ist freiberuflicher Wissensvermittler und Technologieberater fĂŒr Webentwicklung, CodequalitĂ€t und agile Methoden.
(ane [2])


URL dieses Artikels:
https://www.heise.de/-1392378

Links in diesem Artikel:
[1] http://www.goloroden.de/
[2] mailto:ane@heise.de