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
03Coercion & Truthinessthe rules behind ==
04Operatorsthe daily grammar
05Stringsimmutable text
06Numbers & Mathall floats (IEEE-754)
07Dates & Timemonths are 0-indexed
08Arrays · build & findordered lists
09Arrays · transformcopy vs mutate
10Objectskey → value maps
11Destructuring & Restunpack & gather
12Functionsfirst-class values
13this & bindingset by the call site
14Classessyntax over prototypes
15Symbols & Metaprogramminghidden keys & traps
16Control Flowbranch & loop
17Iterators & Generatorslazy sequences
18Promisesa future value
19async / awaitpromises, written flat
20Map & Setkeyed & unique
21JSON & Cloningserialize & copy
22Regular Expressionspattern matching
23Error Handlingthrow & catch
24Modulesimport / export (ESM)
25Console & Debuggingbeyond console.log

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
months are 0-indexednew Date(2024,0,1) is January; getMonth() returns 0–11
+ concatenatesif either side is a string; - * / coerce to number instead
parseInt wants a radixparseInt(s, 10) — be explicit about the base
falsy is a short list0 "" null undefined NaN false; everything else is truthy