Kapitel 9. Operatoren
Inhaltsverzeichnis
Das Buch kaufen
(Werbung, bitte nicht blockieren.)

Kapitel 9. Operatoren

Dieses Kapitel gibt einen Überblick über Operatoren.

Operatoren und Objekte

Alle Operatoren wandeln (wie in Typkonvertierung besprochen) ihre Operanden in entsprechende Typen um. Die meisten Operatoren arbeiten nur mit primitiven Werten (z. B. arithmetische und Vergleichsoperatoren). Das bedeutet, dass Objekte in primitive Typen umgewandelt werden, bevor etwas mit ihnen geschieht. Ein Beispiel, wo das unglücklich ist, ist der Plus-Operator, den viele Sprachen zur Verkettung von Arrays verwenden. Das ist jedoch nicht bei JavaScript der Fall, wo dieser Operator Arrays in Zeichenketten umwandelt und sie anhängt:

> [1, 2] + [3]
'1,23'
> String([1, 2])
'1,2'
> String([3])
'3'

Hinweis

Es gibt keine Möglichkeit, Operatoren in JavaScript zu überladen oder anzupassen, nicht einmal die Gleichheit.

Zuweisungsoperatoren

Es gibt verschiedene Möglichkeiten, den einfachen Zuweisungsoperator zu verwenden:

x = wert
Weist einer zuvor deklarierten Variablen x einen Wert zu
var x = wert
Kombiniert eine Variablendeklaration mit einer Zuweisung
obj.propKey = wert
Setzt eine Eigenschaft
obj['propKey'] = wert
Setzt eine Eigenschaft
arr[index] = wert
Setzt ein Array-Element[10]

Eine Zuweisung ist ein Ausdruck, der zum zugewiesenen Wert ausgewertet wird. Dies ermöglicht Ihnen, Zuweisungen zu verketten. Beispielsweise weist die folgende Anweisung 0 sowohl y als auch x zu

x = y = 0;

Verbundzuweisungsoperatoren

Ein zusammengesetzter Zuweisungsoperator wird als op= geschrieben, wobei op einer von mehreren binären Operatoren ist und = der Zuweisungsoperator ist. Die folgenden beiden Ausdrücke sind äquivalent:

myvar op= value
myvar = myvar op value

Mit anderen Worten, ein zusammengesetzter Zuweisungsoperator op= wendet op auf beide Operanden an und weist das Ergebnis dem ersten Operanden zu. Sehen wir uns ein Beispiel für die Verwendung des Plus-Operators (+) über eine zusammengesetzte Zuweisung an

> var x = 2;
> x += 3
5
> x
5

Die folgenden sind alle zusammengesetzte Zuweisungsoperatoren:

Gleichheitsoperatoren: === vs. ==

JavaScript hat zwei Möglichkeiten, festzustellen, ob zwei Werte gleich sind:

  • Strikte Gleichheit (===) und strikte Ungleichheit (!==) betrachtennur Werte gleichen Typs als gleich.
  • Normale (oder „lenient“) Gleichheit (==) und Ungleichheit (!=) versuchen, Werte unterschiedlicher Typen zu konvertieren, bevor sie sie wie bei strikter (Un-)Gleichheit vergleichen.

Leniente Gleichheit ist in zweierlei Hinsicht problematisch. Erstens ist die Art und Weise, wie die Konvertierung durchgeführt wird, verwirrend. Zweitens können aufgrund der sehr nachsichtigen Operatoren Typfehler länger versteckt bleiben.

Verwenden Sie immer strikte Gleichheit und vermeiden Sie leniente Gleichheit. Sie müssen sich nur mit Letzterer beschäftigen, wenn Sie wissen wollen, warum sie vermieden werden sollte.

Gleichheit ist nicht anpassbar. Operatoren können in JavaScript nicht überladen werden, und Sie können nicht anpassen, wie Gleichheit funktioniert. Es gibt einige Operationen, bei denen Sie den Vergleich oft beeinflussen müssen – zum Beispiel Array.prototype.sort() (siehe Sortieren und Umkehren von Elementen (destruktiv)). Diese Methode akzeptiert optional eine Callback-Funktion, die alle Vergleiche zwischen Array-Elementen durchführt.

Strikte Gleichheit (===, !==)

Werte mit unterschiedlichen Typen sind niemals strikt gleich. Wenn beide Werte denselben Typ haben, dann gelten die folgenden Aussagen:

Fallstrick: NaN

Der spezielle Zahlenwert NaN (siehe NaN) ist nicht gleich sich selbst:

> NaN === NaN
false

Daher müssen Sie andere Mittel verwenden, um ihn zu überprüfen, die in Fallstrick: Überprüfen, ob ein Wert NaN ist beschrieben sind.

Strikte Ungleichheit (!==)

Ein strikter Ungleichheits-Vergleich:

x !== y

ist äquivalent zur Negation eines strikten Gleichheitsvergleichs

!(x === y)

Normale (leniente) Gleichheit (==, !=)

Der Algorithmus für den Vergleich über normale Gleichheit funktioniert wie folgt. Wenn beide Operanden denselben Typ haben (einer der sechs Spezifikationstypen – Undefined, Null, Boolean, Number, String und Object), dann vergleichen Sie sie über strikte Gleichheit.

Andernfalls, wenn die Operanden

  1. undefined und null sind, dann werden sie als lenient gleich betrachtet

    > undefined == null
    true
  2. Eine Zeichenkette und eine Zahl, dann konvertieren Sie die Zeichenkette in eine Zahl und vergleichen beide Operanden über strikte Gleichheit.
  3. Ein Boolean und ein Nicht-Boolean, dann konvertieren Sie den Boolean in eine Zahl und vergleichen lenient (erneut).
  4. Ein Objekt und eine Zahl oder eine Zeichenkette, dann versuchen Sie, das Objekt in einen primitiven Wert umzuwandeln (über den Algorithmus, der in Algorithmus: ToPrimitive()—Konvertierung eines Wertes in einen primitiven Wert beschrieben ist) und vergleichen lenient (erneut).

Andernfalls – wenn keiner der oben genannten Fälle zutrifft – ist das Ergebnis des lenienten Vergleichs false.

Leniente Ungleichheit (!=)

Ein Ungleichheits-Vergleich:

x != y

ist äquivalent zur Negation eines Gleichheitsvergleichs

!(x == y)

Fallstrick: leniente Gleichheit unterscheidet sich von der Konvertierung in einen Boolean

Schritt 3 bedeutet, dass Gleichheit und Konvertierung in einen Boolean (siehe Konvertierung in Boolean) unterschiedlich funktionieren. Wenn Zahlen größer als 1 in einen Boolean konvertiert werden, werden sie true (z. B. in if-Anweisungen). Aber diese Zahlen sind nicht lenient gleich true. Die Kommentare erklären, wie die Ergebnisse berechnet wurden

> 2 == true  // 2 === 1
false
> 2 == false  // 2 === 0
false

> 1 == true  // 1 === 1
true
> 0 == false  // 0 === 0
true

Ähnlich verhält es sich so, dass die leere Zeichenkette gleich false ist, aber nicht alle nicht-leeren Zeichenketten gleich true sind:

> '' == false   // 0 === 0
true
> '1' == true  // 1 === 1
true
> '2' == true  // 2 === 1
false
> 'abc' == true  // NaN === 1
false

Fallstrick: leniente Gleichheit und Zeichenketten

Ein Teil der Lenienz kann nützlich sein, abhängig davon, was Sie möchten:

> 'abc' == new String('abc')  // 'abc' == 'abc'
true
> '123' == 123  // 123 === 123
true

Andere Fälle sind problematisch, aufgrund der Art und Weise, wie JavaScript Zeichenketten in Zahlen umwandelt (siehe Konvertierung in Zahl)

> '\n\t123\r ' == 123  // usually not OK
true
> '' == 0  // 0 === 0
true

Fallstrick: leniente Gleichheit und Objekte

Wenn Sie ein Objekt mit einem Nicht-Objekt vergleichen, wird es in einen primitiven Wert umgewandelt, was zu seltsamen Ergebnissen führt:

> {} == '[object Object]'
true
> ['123'] == 123
true
> [] == 0
true

Zwei Objekte sind jedoch nur dann gleich, wenn sie dasselbe Objekt sind. Das bedeutet, dass Sie keine Wrapper-Objekte wirklich vergleichen können

> new Boolean(true) === new Boolean(true)
false
> new Number(123) === new Number(123)
false
> new String('abc') == new String('abc')
false

Es gibt keine gültigen Anwendungsfälle für ==

Man liest manchmal über gültige Anwendungsfälle für leniente Gleichheit (==). Dieser Abschnitt listet sie auf und weist auf bessere Alternativen hin.

Anwendungsfall: Überprüfung auf undefined oder null

Der folgende Vergleich stellt sicher, dass x weder undefined noch null ist:

if (x != null) ...

Obwohl dies eine kompakte Schreibweise für diese Prüfung ist, verwirrt sie Anfänger, und Experten können nicht sicher sein, ob es sich um einen Tippfehler handelt oder nicht. Wenn Sie also überprüfen möchten, ob x einen Wert hat, verwenden Sie die Standardprüfung auf Wahrhaftigkeit (behandelt in Wahrheitswerte und Falschwerte)

if (x) ...

Wenn Sie präziser sein möchten, sollten Sie eine explizite Prüfung für beide Werte durchführen

if (x !== undefined && x !== null) ...

Anwendungsfall: Arbeiten mit Zahlen in Zeichenketten

Wenn Sie nicht sicher sind, ob ein Wert x eine Zahl oder eine Zahl-als-Zeichenkette ist, können Sie Prüfungen wie die folgenden verwenden:

if (x == 123) ...

Das Vorherige prüft, ob x entweder 123 oder '123' ist. Wiederum ist dies sehr kompakt, und wieder ist es besser, explizit zu sein

if (Number(x) === 123) ...

Anwendungsfall: Vergleichen von Wrapper-Instanzen mit primitiven Werten

Leniente Gleichheit ermöglicht Ihnen den Vergleich von primitiven Werten mit gewrappten primitiven Werten:

> 'abc' == new String('abc')
true

Es gibt drei Gründe gegen diesen Ansatz. Erstens funktioniert leniente Gleichheit nicht zwischen gewrappten primitiven Werten:

> new String('abc') == new String('abc')
false

Zweitens sollten Sie Wrapper ohnehin vermeiden. Drittens, wenn Sie sie doch verwenden, ist es besser, explizit zu sein

if (wrapped.valueOf() === 'abc') ...

Reihenfolgeoperatoren

JavaScript kennt die folgendenReihenfolgeoperatoren:

  • Kleiner als (<)
  • Kleiner als oder gleich (<=)
  • Größer als (>)
  • Größer als oder gleich (>=)

Diese Operatoren funktionieren für Zahlen und für Zeichenketten:

> 7 >= 5
true
> 'apple' < 'orange'
true

Für Zeichenketten sind sie nicht sehr nützlich, da sie Groß- und Kleinschreibung beachten und keine Funktionen wie Akzente gut handhaben (Details siehe Vergleich von Zeichenketten).

Der Algorithmus

Sie werten einen Vergleich aus

x < y

indem Sie die folgenden Schritte durchführen:

  1. Stellen Sie sicher, dass beide Operanden primitive Werte sind. Objekte obj werden über die interne Operation ToPrimitive(obj, Number) in primitive Werte umgewandelt (siehe Algorithmus: ToPrimitive()—Konvertierung eines Wertes in einen primitiven Wert), die obj.valueOf() und möglicherweise obj.toString() aufruft, um dies zu tun.
  2. Wenn beide Operanden Zeichenketten sind, vergleichen Sie sie, indem Sie die 16-Bit-Codeeinheiten (siehe Kapitel 24) lexikografisch vergleichen, die die JavaScript-Zeichen der Zeichenkette darstellen.
  3. Andernfalls konvertieren Sie beide Operanden in Zahlen und vergleichen sie numerisch.

Die anderen Reihenfolgeoperatoren werden ähnlich behandelt.

Der Plus-Operator (+)

Im Großen und Ganzen untersucht der Plus-Operator seine Operanden. Wenn einer davon eine Zeichenkette ist, wird der andere ebenfalls in eine Zeichenkette konvertiert und beide werden verkettet:

> 'foo' + 3
'foo3'
> 3 + 'foo'
'3foo'

> 'Colors: ' + [ 'red', 'green', 'blue' ]
'Colors: red,green,blue'

Andernfalls werden beide Operanden in Zahlen konvertiert (siehe Konvertierung in Zahl) und addiert

> 3 + 1
4
> 3 + true
4

Das bedeutet, dass die Reihenfolge, in der Sie auswerten, wichtig ist

> 'foo' + (1 + 2)
'foo3'
> ('foo' + 1) + 2
'foo12'

Der Algorithmus

Sie werten eine Addition aus

value1 + value2

indem Sie die folgenden Schritte durchführen

  1. Stellen Sie sicher, dass beide Operanden primitive Werte sind. Objekte obj werden über die interne Operation ToPrimitive(obj) in primitive Werte umgewandelt (siehe Algorithmus: ToPrimitive()—Konvertierung eines Wertes in einen primitiven Wert), die obj.valueOf() und möglicherweise obj.toString() aufruft, um dies zu tun. Für Daten, obj.toString() wird zuerst aufgerufen.
  2. Wenn einer der Operanden eine Zeichenkette ist, konvertieren Sie beide in Zeichenketten und geben Sie die Verkettung der Ergebnisse zurück.
  3. Andernfalls konvertieren Sie beide Operanden in Zahlen und geben Sie die Summe der Ergebnisse zurück.

Operatoren für Booleans und Zahlen

Die folgenden Operatoren haben nur Operanden eines einzigen Typs und erzeugen auch Ergebnisse dieses Typs. Sie werden an anderer Stelle behandelt.

Boolean-Operatoren

Zahlenoperatoren

Spezielle Operatoren

Hier überprüfen wir spezielle Operatoren, nämlich den bedingten Operator, den Kommaoperator und den void-Operator.

«left» , «right»

Der Kommaoperator wertet beide Operanden aus und gibt das Ergebnis von right zurück. Im Großen und Ganzen tut er für Ausdrücke, was das Semikolon für Anweisungen tut.

Dieses Beispiel zeigt, dass der zweite Operand zum Ergebnis des Operators wird

> 123, 'abc'
'abc'

Dieses Beispiel zeigt, dass beide Operanden ausgewertet werden

> var x = 0;
> var y = (x++, 10);

> x
1
> y
10

Der Kommaoperator ist verwirrend. Es ist besser, nicht clever zu sein und stattdessen zwei separate Anweisungen zu schreiben, wann immer Sie können.

Der void-Operator

Die Syntax für den void-Operator ist:

void «expr»

was expr auswertet und undefined zurückgibt. Hier sind einige Beispiele

> void 0
undefined
> void (0)
undefined

> void 4+7  // same as (void 4)+7
NaN
> void (4+7)
undefined

> var x;
> x = 3
3
> void (x = 5)
undefined
> x
5

Wenn Sie also void als Funktion implementieren, sieht es so aus

function myVoid(expr) {
    return undefined;
}

Der void-Operator ist eng mit seinem Operanden verbunden, verwenden Sie daher Klammern, wo nötig. Zum Beispiel bindet void 4+7 als (void 4)+7.

Warum hat JavaScript einen void-Operator?

Laut JavaScript-Erfinder Brendan Eich hat er ihn zur Sprache hinzugefügt, um javascript: Links (einer der oben genannten Anwendungsfälle) zu unterstützen:

Ich habe den void-Operator vor der Veröffentlichung von Netscape 2 zu JS hinzugefügt, um es einfach zu machen, jeden nicht-undefinierten Wert in einer javascript: URL zu verwerfen.[12]

Werte über typeof und instanceof kategorisieren

Wenn Sie einen Wert kategorisieren möchten, müssen Sie leider zwischen primitiven Werten und Objekten unterscheiden (siehe Kapitel 8) in JavaScript

  • Der Operator typeof unterscheidet primitive Werte von Objekten und bestimmt die Typen von primitiven Werten.
  • Der Operator instanceof bestimmt, ob ein Objekt eine Instanz eines gegebenen Konstruktors ist. Weitere Informationen zur objektorientierten Programmierung in JavaScript finden Sie in Kapitel 17.

typeof: Primitive Werte kategorisieren

Der typeof-Operator:

typeof «value»

gibt eine Zeichenkette zurück, die beschreibt, welche Art von Wert value ist. Hier sind einige Beispiele

> typeof undefined
'undefined'
> typeof 'abc'
'string'
> typeof {}
'object'
> typeof []
'object'

typeof wird verwendet, um primitive Werte und Objekte zu unterscheiden und primitive Werte zu kategorisieren (die nicht von instanceof behandelt werden können). Leider sind die Ergebnisse dieses Operators nicht vollständig logisch und entsprechen nur lose den Typen der ECMAScript-Spezifikation (die in JavaScript-Typen erklärt werden)

OperandErgebnis

undefined, nicht deklarierte Variable

'undefined'

null

'object'

Boolean-Wert

'boolean'

Zahlenwert

'number'

String-Wert

'string'

Funktion

'function'

Alle anderen normalen Werte

'object'

(Von der Engine erzeugter Wert)

JavaScript-Engines dürfen Werte erstellen, für die typeof beliebige Zeichenketten zurückgibt (unterschiedlich von allen in dieser Tabelle aufgeführten Ergebnissen).

Die erste JavaScript-Engine stellte JavaScript-Werte als 32-Bit-Wörter dar. Die niedrigsten 3 Bits eines solchen Wortes wurden als Typ-Tag verwendet, um anzugeben, ob der Wert ein Objekt, eine Ganzzahl, eine Gleitkommazahl, eine Zeichenkette oder ein Boolean war (wie Sie sehen können, speicherte selbst diese frühe Engine Zahlen bereits als Ganzzahlen, wenn möglich).

Das Typ-Tag für Objekte war 000. Um den Wert null darzustellen, verwendete die Engine den Maschinen-NULL-Zeiger, ein Wort, bei dem alle Bits Null sind. typeof prüfte das Typ-Tag, um den Werttyp zu bestimmen, weshalb es null als Objekt meldete.[13]

Der instanceof-Operator:

«value» instanceof «Constr»

bestimmt, ob value vom Konstruktor Constr oder einem Unterkonstruktor erstellt wurde. Hier sind einige Beispiele

> {} instanceof Object
true
> [] instanceof Array  // constructor of []
true
> [] instanceof Object  // super-constructor of []
true

Wie erwartet, ist instanceof für die Nicht-Werte undefined und null false

> undefined instanceof Object
false
> null instanceof Object
false

Aber es ist auch false für alle anderen primitiven Werte

> 'abc' instanceof Object
false
> 123 instanceof Object
false

Details zu instanceof finden Sie in Der instanceof-Operator.



[10] Streng genommen ist das Setzen eines Array-Elements ein Spezialfall des Setzens einer Eigenschaft.

[11] Danke an Brandon Benvie (@benvie), der mich auf die Verwendung von void für IIFEs aufmerksam gemacht hat.

[13] Danke an Tom Schuster (@evilpies) für den Hinweis auf den Quellcode der ersten JavaScript-Engine.

Weiter: 10. Booleans