Skip to main content

Expressions & Operators

An expression resolves to a value (3 + 4, x = 7); a statement is a complete instruction. Any expression used on its own becomes an expression statement. Operators combine operands; precedence decides grouping, associativity breaks ties. Use () to override: (1 + 2) * 39 vs 1 + 2 * 37.

Assignment

= returns the assigned value, which is why chaining works — and why it evaluates left-to-right but groups right-to-left:

y = x = f(); // f() runs once → assigned to x → then to y
y = [f(), x = g()]; // logs "F!" then "G!"; y = [2, 3], x = 3

Avoid const z = y = x = f() — only z is declared; x/y leak as globals in sloppy mode.

Compound forms: += -= *= /= %= **=, bitwise <<= >>= >>>= &= ^= |=, and the logical ones:

x &&= f(); // x && (x = f())  — assign only if x truthy
x ||= f(); // x || (x = f()) — assign only if x falsy
x ??= f(); // x ?? (x = f()) — assign only if x null/undefined

Destructuring unpacks into targets: const [a, b] = arr; and const { x, y } = obj;.

Gotcha — assigning a property to a primitive fails silently (throws in strict mode):

const val = 0;
val.x = 3;
val.x; // undefined

Comparison

coercion3 == "3"
== / !=yestrue
=== / !==nofalse

Prefer ===. Relational < <= > >= compare numerically for numbers, lexicographically (Unicode) for strings — so "2" < "12" is false but 2 < 12 is true.

Arithmetic

+ - * / % (remainder), ** (exponentiation, right-associative: 2 ** 3 ** 2 = 512). 1 / 0Infinity (not an error). ++/-- differ by position: prefix returns the new value, postfix returns the old.

Bitwise

Operands coerced to 32-bit signed ints. Logical & | ^ ~; ~x === -(x + 1). Shifts: << (left), >> (sign-propagating right), >>> (zero-fill right — no BigInt equivalent).

Logical & short-circuit

Each returns an operand, not a boolean, and stops early:

a && b // first falsy, else last  ("Cat" && "Dog" → "Dog")
a || b // first truthy, else last (false || "Cat" → "Cat")
a ?? b // a unless a is null/undefined

?? beats || for defaults because it treats 0, "", false as valid (only nullish triggers the fallback). ! coerces to boolean and inverts.

BigInt

Most operators work, but: no >>>, and you cannot mix BigInt with Number — 1n + 2 throws. Convert explicitly (Number(1n) / BigInt(2)). Division truncates: 1n / 2n0n. Mixed comparison is fine: 3 > 2ntrue.

Other operators

  • Ternary: cond ? a : b — the only three-operand operator.
  • Comma: evaluates both, returns the last; legit use is for (i=0, j=9; …; i++, j--).
  • String: + concatenates; += appends.
  • Unary: +x / -x (numeric convert / negate), ! (boolean invert), typeof, void expr (→ undefined), delete obj.prop (returns false on non-configurable; on arrays leaves a hole, length unchanged).
  • Relational: in (property exists, including inherited — "length" in []true), instanceof (walks the prototype chain).

typeof quirks

typeof null        // "object"   ← historic bug
typeof [] // "object"
typeof function(){}// "function"
typeof undeclared // "undefined" (safe — no ReferenceError)

Left-hand-side expressions

  • new Ctor(args) — instantiate.
  • super(...) / super.method() — parent constructor/methods.
  • Spread ... — expand iterables/objects in calls and literals.
  • Property access: obj.x, obj["x"], obj[key].
  • Optional chaining ?. — short-circuits to undefined if the left side is null/undefined: a?.b, a?.[k], fn?.().

Precedence (high → low)

() access/call → ** → unary (! ~ + - ++ -- typeof void delete) → * / %+ - → shifts → relational (< <= > >= in instanceof) → equality → &^|&&||???: → assignment → ,