Quick Reference · the language, through ECMAScript 2024

JS JavaScript cheat sheet

One rule map for the whole language: every value is either a primitive (copied) or an object (shared by reference); code runs top-to-bottom on one thread, and anything asynchronous is parked and later resumed by the event loop. Learn those three ideas and the syntax stops being a list to memorize. Recent additions are tagged by ECMAScript year.

syntax & operators functions & classes values & data async & iteration built-ins & modules gotcha ES20xx added in that year most common

Distilled & cross-checked across: developer.mozilla.org (MDN) · tc39.es/ecma262 · 2ality.com · javascript.info · quickref.me · verified against Node v22

The one mental model for async — the event loop
RUNS NOW · ONE THREAD Call Stack last-in first-out main() parse() format() frame pushed on call, popped on return Web / Node APIs setTimeout · fetch · DOM events · fs I/O timers & I/O run OUTSIDE the stack Microtask Queue Promise .then · await · queueMicrotask drained COMPLETELY, first Macrotask Queue setTimeout · setInterval · I/O · UI events just ONE per loop turn Event Loop stack empty? 1 · async call offloaded 2 · on complete → enqueue callback 3 · when stack is empty, push the next callback back onto it

The ECMAScript timeline

One yearly release since ES2015. The five most recent editions carry nearly everything you'd call "modern JavaScript" — highlighted in violet below.

ES2015ES6 · the big bang
  • let / const, arrow =>
  • classes, template literals
  • destructuring, spread, modules
  • Promises, Map/Set, generators
ES2016–19steady drip
  • **, Array.includes
  • async/await (ES2017)
  • object spread, Promise.finally
  • flat, Object.fromEntries
ES2020
  • ?. optional chaining
  • ?? nullish coalescing
  • BigInt, globalThis
  • Promise.allSettled, dynamic import()
ES2021
  • &&= ||= ??=
  • String.replaceAll
  • Promise.any
  • numeric separators 1_000
ES2022
  • class #private fields + static blocks
  • .at(-1), Object.hasOwn
  • top-level await
  • error cause, RegExp /d
ES2023
  • findLast / findLastIndex
  • copy methods toSorted toReversed
  • toSpliced, with
  • Symbols as WeakMap keys
ES2024
  • Object.groupBy / Map.groupBy
  • Promise.withResolvers
  • RegExp /v flag, resizable ArrayBuffer
  • String.isWellFormed / toWellFormed
01Declaring Variablesblock scope by default
02Types & Checking7 primitives + object
03Operatorsthe daily grammar
04Stringsimmutable text
05Numbers & Mathall floats (IEEE-754)
06Arrays · build & findordered lists
07Arrays · transformcopy vs mutate
08Objectskey → value maps
09Destructuring & Restunpack & gather
10Functionsfirst-class values
11this & bindingset by the call site
12Classessyntax over prototypes
13Control Flowbranch & loop
14Iterators & Generatorslazy sequences
15Promisesa future value
16async / awaitpromises, written flat
17Map & Setkeyed & unique
18JSON & Cloningserialize & copy
19Regular Expressionspattern matching
20Error Handlingthrow & catch
21Modulesimport / export (ESM)

Four ideas worth seeing

The mental models behind most JavaScript surprises. Get these and the gotchas below stop being mysterious.

Primitives copy · objects share

Assigning a primitive copies its value. Assigning an object copies only the reference — both names point at one thing.

let b = a (number) → independent copies a5 copy b = 99 a still 5 ✓ let p = o (object) → one shared object o p { n: 9 } p.n = 9 changes o.n too ⚠

The prototype chain

Property lookup walks up __proto__ links until it finds the key — or hits null. That chain is how inheritance actually works.

[1, 2, 3] the instance Array.prototype .map .filter … Object.prototype .hasOwnProperty … null __proto__ __proto__ top lookup for .foo walks all three, finds nothing → undefined

Microtasks jump the queue

All synchronous code runs first, then the loop drains every promise callback, and only then runs one timer. Same example, exact output.

console.log(1) setTimeout(() => log('timeout')) Promise.resolve().then(() => log('promise')) console.log(4) 1 · SYNC (now) 1 4 2 · MICROTASKS (drain all) promise 3 · MACROTASK (one) timeout output → 1 · 4 · promise · timeout

What this is — read the call site

Ignore where a function is defined; look at how it's called. Four rules, checked top to bottom — arrows are the exception.

new Thing() → the new instance obj.method() → obj (left of the dot) f.call(ctx) / bind → ctx you passed f() (bare) → undefined (strict) () => this Arrow: no own this — frozen to the surrounding scope at definition. Checked in order. The arrow rule wins over all of them — which is exactly why arrows are the safe choice for callbacks.

Worth memorizing

=== not ==== coerces types ([] == ![] is true); === never lies
typeof nullreports "object" — a permanent 1995 bug
NaN !== NaNtest with Number.isNaN(x), never x === NaN
0.1 + 0.2= 0.30000000000000004 — round before comparing money
?? not ||?? keeps 0 / "" / false; || throws them away
sort() is stringsand mutates — use [...a].sort((x,y)=>x-y)
copy is shallow{...o} / [...a] nest by reference; deep = structuredClone
const isn't frozenthe binding is fixed; the object's contents still mutate
arrows have no thisnor arguments — great for callbacks, wrong for methods
micro before macroevery promise callback runs before the next setTimeout
async ⇒ Promisean async fn always returns one; await unwraps it
let over varvar is function-scoped & hoisted; the loop-closure trap needs let
for…of vs inof = values (arrays); in = keys (objects)
map ≠ forEachmap returns a new array; forEach returns undefined