JavaScript besitzt eine ungewöhnlich breite Palette an Operatoren — gewachsen über knapp drei Jahrzehnte, mit Altlasten aus der Frühzeit und modernen Ergänzungen wie Logical Assignment (ES2021) oder Nullish Coalescing (ES2020). Wer den Sprachkern beherrschen will, muss die Kategorien, ihre Coercion-Regeln und die Precedence-Tabelle im Kopf haben — sonst werden klassische Fallen wie '5' + 3 === '53', NaN === NaN als false oder die Rechts-Assoziativität von ** zu unauffindbaren Bugs. Dieser Artikel zeigt alle Operator-Kategorien systematisch, demonstriert die häufigsten Stolperfallen mit Code und liefert eine kompakte Precedence-Referenz für den Alltag.

Operator-Kategorien im Überblick

Die ECMAScript-Spezifikation gliedert Operatoren in mehrere Gruppen, die sich primär nach Stelligkeit (unär, binär, ternär), Position (prefix, infix, postfix) und semantischer Funktion unterscheiden. Die folgende Tabelle fasst alle Kategorien zusammen und nennt einen typischen Vertreter pro Gruppe.

KategorieBeispiel-OperatorenStelligkeitAnmerkung
Arithmetisch+ - * / % **binär** ab ES2016, rechts-assoziativ
Vergleich (lose)== !=binärmit Coercion — meist vermeiden
Vergleich (strikt)=== !==binärempfohlener Standard
Relational< > <= >=binärnumerisch oder lexikografisch
Logisch&& || !binär/unärShort-Circuit, returnt last evaluated
Nullish Coalescing??binärES2020 — nur null/undefined
Logical Assignment&&= ||= ??=binärES2021 — Defaults knapp
Bitweise& | ^ ~ << >> >>>binär/unär32-Bit-Integer-Konvertierung
Zuweisung= += -= *= /= %= **= ...binär= ist rechts-assoziativ
Conditional (Ternär)? :ternäreinzige ternäre Form
Comma,binärniedrigste Precedence
Spread / Rest...unärkontextabhängig
Optional Chaining?. ?.[] ?.()binärES2020
Type-Operatorentypeof instanceof inunär/binärLaufzeit-Reflection
Sonstige Unärdelete void newunärdelete nur auf Object-Properties
String-Konkatenation+binärdieselbe Form wie Addition

Die Reihenfolge in der Tabelle entspricht nicht der Precedence — dafür gibt es Section 09. Die Kategorien sind hier semantisch gruppiert.

Arithmetische Operatoren

Die sechs klassischen arithmetischen Operatoren + - * / % ** arbeiten auf Numbers (und seit ES2020 auch auf BigInt — aber nicht gemischt). Drei Eigenheiten sind merkenswert: die Float-Falle bei /, der Power-Operator ** mit Rechts-Assoziativität und das BigInt-Mix-Verbot.

JavaScript arithmetik.js
// 1) Float-Falle bei /
console.log(0.1 + 0.2);         // 0.30000000000000004
console.log(1 / 3);             // 0.3333333333333333
console.log(10 / 3);            // 3.3333333333333335 (kein Integer-Divide)

// 2) Modulo behält Vorzeichen des Dividenden
console.log(-7 % 3);            // -1  (nicht 2 wie in Python)
console.log(7 % -3);            //  1

// 3) Power-Operator ** ist RECHTS-assoziativ
console.log(2 ** 3 ** 2);       // 512  — also 2 ** (3 ** 2) = 2 ** 9
console.log((2 ** 3) ** 2);     // 64

// 4) BigInt-Mix-Verbot
// console.log(1n + 1);         // TypeError: Cannot mix BigInt and other types
console.log(1n + 1n);           // 2n
Output
0.30000000000000004
0.3333333333333333
3.3333333333333335
-1
1
512
64
2n

Für ganzzahlige Division ohne Rest gibt es keinen eigenen Operator — das übliche Idiom ist Math.trunc(a / b) oder bei nicht-negativen Werten (a / b) | 0 (siehe Section 07).

String-Konkatenation und implizite Coercion

Der +-Operator ist überladen: sind beide Operanden Numbers, wird addiert; ist mindestens einer ein String, wird konkateniert. Die übrigen arithmetischen Operatoren (-, *, /, %, **) erzwingen dagegen Number-Coercion. Diese Asymmetrie ist eine der berüchtigtsten Fallen der Sprache.

JavaScript plus-coercion.js
// + mit String konkateniert (links-assoziativ ausgewertet)
console.log('5' + 3);           // '53'
console.log(3 + '5');           // '35'
console.log(1 + 2 + '3');       // '33'   — erst 1+2=3, dann '3'+'3'
console.log('1' + 2 + 3);       // '123'  — erst '12', dann '123'

// - * / % erzwingen Number-Coercion
console.log('5' - 3);           //   2
console.log('5' * '2');         //  10
console.log('10' / '4');        //   2.5
console.log('5' - 'foo');       // NaN

// unärer + als expliziter Number-Cast (idiomatisch)
console.log(+'42');             // 42
console.log(+'');               //  0
console.log(+'abc');            // NaN
Output
53
35
33
123
2
10
2.5
NaN
42
0
NaN

Sicheres Pattern: numerische Eingaben vor der Berechnung explizit casten — entweder per Number(x), parseFloat(x), parseInt(x, 10) oder unärem +x. So entsteht Coercion deterministisch und an einer einzigen, gut sichtbaren Stelle.

Vergleichs-Operatoren

JavaScript kennt zwei Gleichheits-Familien: lose Gleichheit (==, !=) mit Coercion und strikte Gleichheit (===, !==) ohne Coercion. Dazu kommen die relationalen Operatoren < > <= >=. Drei Sonderfälle muss man kennen: das NaN-Verhalten, die null/undefined-Sonderregel von == und Object.is als Drittweg.

JavaScript equality-coercion.js
// == mit Coercion — die berüchtigten Fälle
console.log(0  == '');          // true
console.log(0  == '0');         // true
console.log('' == '0');         // false  (beides Strings, kein Cast)
console.log(null == undefined); // true   (Sonderregel)
console.log(null == 0);         // false  (== koerziert null nicht zu 0)
console.log([] == false);       // true
console.log([] == 0);           // true
console.log([0] == false);      // true

// === ohne Coercion — kein Spielraum
console.log(0  === '');         // false
console.log(0  === '0');        // false
console.log(null === undefined);// false
Output
true
true
false
true
false
true
true
true
false
false
false

Das NaN-Verhalten ist der zweite Fallstrick. NaN ist als einziger Wert nicht gleich sich selbst — auch nicht mit ===. Für Tests auf NaN gibt es deshalb Number.isNaN() oder Object.is().

JavaScript nan-und-object-is.js
console.log(NaN === NaN);          // false  — NaN ist nie gleich
console.log(NaN ==  NaN);          // false

console.log(Number.isNaN(NaN));    // true   — der saubere Test
console.log(Number.isNaN('foo'));  // false  (Coercion-frei!)
console.log(isNaN('foo'));         // true   (legacy: koerziert erst)

// Object.is unterscheidet zusätzlich +0 / -0
console.log(Object.is(NaN, NaN));  // true
console.log(Object.is(+0, -0));    // false  (=== würde true liefern)
console.log(+0 === -0);            // true
Output
false
false
true
false
true
true
false
true

Faustregel: standardmäßig ===/!== verwenden. == nur dann, wenn man bewusst die null == undefined-Sonderregel ausnutzt — etwa als kompakter Nullish-Check if (x == null). Alle anderen Coercion-Fälle sind in modernem Code Bug-Magnete.

Logische Operatoren mit Short-Circuit

&&, || und ! sind die klassischen logischen Operatoren — mit zwei JavaScript-Eigenheiten: Short-Circuit-Evaluation (die rechte Seite wird nicht ausgewertet, wenn die linke das Ergebnis schon bestimmt) und die Tatsache, dass && und || nicht zwangsläufig einen Boolean zurückgeben, sondern den letzten ausgewerteten Operanden in dessen ursprünglichem Typ.

JavaScript logical-short-circuit.js
// && returnt den ersten falsy oder den letzten Wert
console.log(1 && 2);            // 2     — beide truthy → letzter
console.log(0 && 2);            // 0     — erster falsy
console.log('a' && 'b' && 'c'); // 'c'

// || returnt den ersten truthy oder den letzten Wert
console.log(0 || 'fallback');   // 'fallback'
console.log('x' || 'y');        // 'x'
console.log(null || 0 || '');   // ''    — alle falsy, letzter Wert

// ! liefert IMMER Boolean (Coercion über ToBoolean)
console.log(!0);                // true
console.log(!'');               // true
console.log(!{});               // false — Objekte sind truthy
console.log(!![]);              // true  — doppeltes ! als Boolean-Cast

// Short-Circuit für Side-Effects
const obj = null;
obj && obj.dispose();           // sicher: dispose() wird nicht aufgerufen
Output
2
0
c
fallback
x

true
true
false
true

Die Tatsache, dass || den letzten ausgewerteten Wert zurückgibt, hat lange das Default-Pattern const port = config.port || 8080; getragen. Seit ES2020 ist dieses Pattern wegen der Falsy-Falle (Eingabe 0 würde überschrieben) durch ?? abgelöst — Details im Artikel zu Optional Chaining & Nullish.

Logical Assignment Operators (ES2021)

ES2021 hat die drei Operatoren &&=, ||= und ??= ergänzt — Kurzschreibweisen für die typischen Default-Patterns. Sie unterscheiden sich semantisch in der Bedingung, unter der zugewiesen wird, und sie werten den linken Operanden nur einmal aus.

JavaScript logical-assignment.js
// Klassisches Pattern vorher
let cache = undefined;
cache = cache || {};                  // alt
cache ||= {};                         // neu — kürzer, ein Lookup

// Drei Varianten — unterschiedliche Bedingungen
let a = 0;        a ||= 5;            // a=5  (0 ist falsy)
let b = 0;        b ??= 5;            // b=0  (0 ist NICHT nullish)
let c = 1;        c &&= 5;            // c=5  (1 ist truthy → assign)
let d = null;     d &&= 5;            // d=null (null ist falsy → skip)

console.log(a, b, c, d);              // 5 0 5 null

// Defaults für Object-Properties — extrem kompakt
const opts = { timeout: 0 };
opts.retries ??= 3;                   // setzt nur, wenn nullish
opts.timeout ??= 5000;                // 0 bleibt erhalten!
opts.headers ??= {};
console.log(opts);
// { timeout: 0, retries: 3, headers: {} }
Output
5 0 5 null
{ timeout: 0, retries: 3, headers: {} }

Wichtiger Unterschied zur Hand-geschriebenen Variante: obj.prop ??= value ruft den Setter nur dann auf, wenn der Wert tatsächlich zugewiesen wird. obj.prop = obj.prop ?? value ruft den Setter immer auf — auch wenn der bestehende Wert beibehalten wird. In Klassen mit teuren Settern oder Reaktivitäts-Triggern (Vue, MobX) ist das ein messbarer Unterschied.

Bitweise Operatoren

JavaScript hat keinen nativen Integer-Typ — alle Numbers sind 64-Bit-IEEE-754-Floats. Die bitweisen Operatoren & | ^ ~ << >> >>> sind dennoch verfügbar; sie konvertieren ihre Operanden intern zu vorzeichenbehafteten 32-Bit-Integern, führen die Operation aus und konvertieren das Ergebnis zurück. Das hat zwei Konsequenzen: bei Werten über 2^31 - 1 gibt es Overflow, und die Operation kostet zwei Konvertierungen.

JavaScript bitweise.js
// Standard-Operationen
console.log(0b1100 & 0b1010);         // 0b1000 = 8
console.log(0b1100 | 0b1010);         // 0b1110 = 14
console.log(0b1100 ^ 0b1010);         // 0b0110 = 6
console.log(~5);                      // -6  (bitweise NOT)

// Shift-Operatoren
console.log(1 << 4);                  // 16
console.log(-8 >> 1);                 // -4  (sign-propagating)
console.log(-8 >>> 1);                // 2147483644  (unsigned)

// Klassische Tricks
console.log(5.7 | 0);                 // 5    — Truncate via |0
console.log(-5.7 | 0);                // -5   — gegen 0, nicht floor!
console.log(5.7 << 0);                // 5    — gleicher Effekt
console.log(~~5.7);                   // 5    — doppeltes ~

// 32-Bit-Overflow ab 2^31
console.log((2 ** 31) | 0);           // -2147483648  (Overflow!)
console.log((2 ** 32) | 0);           // 0            (komplett weg)
Output
8
14
6
-6
16
-4
2147483644
5
-5
5
5
-2147483648
0

In modernem Code sind bitweise Operatoren selten — die Tricks (x | 0 als Truncate) sind durch Math.trunc(x) ersetzt, das auch jenseits der 32-Bit-Grenze korrekt arbeitet. Reale Use-Cases bleiben: Flag-Bitmasks (PERMISSION_READ | PERMISSION_WRITE), Hashing, Canvas-Pixel-Manipulation, RGBA-Packing und Low-Level-Protokolle.

Type-Operatoren und sonstige Unär-Formen

Fünf weitere Operatoren sind Pflichtwissen: typeof, instanceof, in, delete und void. Jeder hat eine kleine Eigenheit, an der Anfänger regelmäßig scheitern.

JavaScript type-operators.js
// typeof — gibt einen String, kennt 8 Werte
console.log(typeof undefined);        // 'undefined'
console.log(typeof null);             // 'object'    ← historischer Bug!
console.log(typeof 42);               // 'number'
console.log(typeof 1n);               // 'bigint'
console.log(typeof 'x');              // 'string'
console.log(typeof true);             // 'boolean'
console.log(typeof Symbol());         // 'symbol'
console.log(typeof function(){});     // 'function'  ← eigentlich Object
console.log(typeof {});               // 'object'
console.log(typeof []);               // 'object'    ← Array.isArray() nutzen

// typeof ist sicher gegenüber undeklarierten Variablen
console.log(typeof gibtsNicht);       // 'undefined' (kein ReferenceError)

// instanceof — Prototypen-Check
console.log([] instanceof Array);     // true
console.log([] instanceof Object);    // true (Array erbt von Object)

// in — Property-Existenz (inkl. Prototype-Chain)
console.log('length' in []);          // true
console.log('toString' in {});        // true (geerbt)

// delete — nur auf Object-Properties
const obj = { a: 1 };
console.log(delete obj.a);            // true
console.log(obj);                     // {}

// void — wirft Wert weg, returnt undefined
console.log(void 0);                  // undefined
console.log(void 'irgendwas');        // undefined
Output
undefined
object
number
bigint
string
boolean
symbol
function
object
object
undefined
true
true
true
true
true
{}
undefined
undefined

typeof null === 'object' ist ein Spec-Bug aus der ersten Version von 1995, der aus Backwards-Compatibility-Gründen nie gefixt wurde. Ein TC39-Proposal in 2010 hat den Fix wegen Kompatibilitäts-Risiken zurückgezogen.

Operator-Precedence

Die Precedence-Tabelle bestimmt, wie ein Ausdruck ohne Klammern geparst wird. Höhere Precedence bindet enger. Bei gleicher Precedence entscheidet die Assoziativität — die meisten binären Operatoren sind links-assoziativ, Zuweisung und ** sind rechts-assoziativ.

PrioOperatorAssoz.Beispiel
18Grouping ( … )n/a(a + b) * c
17Member ., [], ?., new …()left-to-rightobj.prop, arr[0]
16new ohne argsright-to-leftnew Foo
15Postfix a++, a--n/ai++
14Unär ! ~ + - ++a --a typeof void delete awaitright-to-left!found, +x
13**right-to-left2 ** 3 ** 2 === 512
12* / %left-to-righta * b / c
11+ -left-to-righta + b - c
10<< >> >>>left-to-rightflags << 1
9< <= > >= in instanceofleft-to-rightx in obj
8== != === !==left-to-righta === b
7&left-to-rightmask & 0xFF
6^left-to-righta ^ b
5|left-to-rightflags | MASK
4&&left-to-righta && b
3||, ??left-to-righta || b, a ?? b
2? :, = += -= ... &&= ||= ??=right-to-lefta ? b : c, x = y = 5
1,left-to-rightfor (i=0, j=0; ...)

Drei klassische Fallen:

JavaScript precedence-fallen.js
// 1) ! bindet enger als && — fast immer wie gewünscht
const found = 0;
const a = !found && 'leer';           // OK: (!found) && 'leer'
console.log(a);                       // 'leer'

// 2) ?? darf NICHT direkt mit || / && gemischt werden
// const x = a || b ?? c;             // SyntaxError!
// Klammern Pflicht:
const x = (1 || 2) ?? 3;              // OK
console.log(x);                       // 1

// 3) ** ist rechts-assoziativ
console.log(2 ** 3 ** 2);             // 512  (= 2 ** 9)
console.log((2 ** 3) ** 2);           // 64

// 4) Comma hat niedrigste Precedence
const y = (1, 2, 3);                  // 3 — letzter Wert
// const z = 1, 2, 3;                 // SyntaxError als Statement!
Output
leer
1
512
64

Faustregel für lesbaren Code: bei Mischung verschiedener Operator-Familien (Logik mit Vergleich, Bitweise mit Logik, ?? mit ||/&&) immer Klammern setzen — auch wenn die Precedence-Tabelle den Default eindeutig macht. Linter wie ESLint haben dafür die Regel no-mixed-operators.

Spread und Rest

... ist syntaktisch derselbe Token in zwei Rollen: Spread zerlegt ein Iterable in einzelne Elemente (am Aufruf- oder Literal-Ort), Rest sammelt mehrere Argumente in ein Array (in Function-Parametern oder Destructuring-Pattern).

JavaScript spread-rest.js
// SPREAD — am Verbrauchs-Ort
const a = [1, 2, 3];
const b = [...a, 4, 5];               // [1, 2, 3, 4, 5]   Array-Literal
const obj = { x: 1, y: 2 };
const merged = { ...obj, y: 9, z: 3 };// { x: 1, y: 9, z: 3 }
console.log(Math.max(...a));          // 3                 Function-Call
console.log(b);
console.log(merged);

// REST — am Definitions-Ort
function sum(first, ...rest) {        // rest = restliche Args
    return first + rest.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4));         // 10

// Rest im Destructuring
const [head, ...tail] = [10, 20, 30];
console.log(head, tail);              // 10 [ 20, 30 ]

const { name, ...metadata } = { name: 'X', age: 1, role: 'a' };
console.log(name, metadata);          // X { age: 1, role: 'a' }
Output
3
[ 1, 2, 3, 4, 5 ]
{ x: 1, y: 9, z: 3 }
10
10 [ 20, 30 ]
X { age: 1, role: 'a' }

Wichtige Notizen zu Operatoren

`==` macht Coercion mit komplexen Regeln — fast immer `===` nehmen

Die Coercion-Tabelle für lose Gleichheit ist sieben Zeilen lang und hat Sonderfälle: 0 == '' ist true, 0 == '0' ist true, aber '' == '0' ist false. null == undefined ist true, sonst koerziert == null nicht. Bewusste Ausnahme: if (x == null) als kompakter Nullish-Check.

`typeof null === 'object'` — historischer Bug, nie gefixt

In der ersten JavaScript-Implementierung 1995 kodierten die Engine-Tags null als Object-Pointer mit Wert 0. Der Fix wurde 2010 als TC39-Proposal eingebracht und wieder zurückgezogen, weil zu viel Bestand-Code auf dem Verhalten aufbaut. Für Null-Checks deshalb x === null statt typeof.

`typeof function` gibt `'function'` — Sonderfall der Spec

Funktionen sind technisch Objects (genauer: Callable-Objects), aber die Spec macht für typeof eine Ausnahme. Das ist Convenience für Type-Guards — in der Realität gibt es zudem auch Proxy-Objekte und manche WebIDL-Hosts, die als 'function' reporten, ohne Function-Instanzen zu sein.

`&&` und `||` returnen NICHT immer Boolean

Short-Circuit gibt den letzten ausgewerteten Operanden zurück — in dessen ursprünglichem Typ. 'a' && 'b' ist 'b', 0 || 'fallback' ist 'fallback'. Wer einen Boolean braucht, muss explizit casten: !!(a && b) oder Boolean(a && b).

Bitweise konvertieren zu 32-Bit-Integer — Overflow ab 2^31

(2 ** 31) | 0 liefert -2147483648, (2 ** 32) | 0 ist 0. Für sicheres Truncating großer Zahlen Math.trunc() nutzen — funktioniert bis zu Number.MAX_SAFE_INTEGER. Für 64-Bit-Bit-Operationen gibt es BigInt-Bitwise (1n & 2n).

`**` ist rechts-assoziativ — `2 ** 3 ** 2` ist `2 ** 9`, nicht `8 ** 2`

Eine der wenigen rechts-assoziativen binären Operationen, im Einklang mit der Mathematik (a^b^c = a^(b^c)). Zusätzlich ist -2 ** 2 ein SyntaxError — die Spec verlangt explizite Klammerung: (-2) ** 2 oder -(2 ** 2), weil sonst unklar wäre, ob - oder ** enger bindet.

Comma-Operator ist eine Expression — selten genutzt, in `for(;;)` wichtig

(a, b) evaluiert beide Ausdrücke und gibt b zurück. Im for-Header der einzige Ort, wo mehrere Statements zusammen müssen: for (let i=0, j=10; i<j; i++, j--). Außerhalb davon meist verwirrend und durch separate Statements ersetzen.

`void 0` ist eine sichere `undefined`-Konstante — historisch begründet

In ES3 (vor 2009) konnte undefined als globale Variable überschrieben werden. Sicherheitsbewusste Bibliotheken nutzten deshalb void 0, das immer den primitiven undefined-Wert liefert. Seit ES5 ist undefined read-only — aber void 0 ist außerdem in Minifiern oft kürzer als das 9-Zeichen-Identifier-undefined.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Syntax & Sprachkern

Zur Übersicht