JavaScript

A comprehensive, interactive reference covering every major concept — from primitive types and closures to async/await, DOM APIs, and modern ES2024 features.

ES2024 DOM APIs Async OOP ● 22 Live Demos

§ 01

Variables & Data Types

JavaScript has 8 data types — 7 primitives (stored by value) and 1 complex type, Object (stored by reference). typeof reveals the type at runtime.

TYPES REFERENCE
number
42, 3.14, NaN, Infinity
primitive
string
"hello", 'world', `template`
primitive
boolean
true / false
primitive
null
null
intentional empty
undefined
undefined
not assigned
symbol
Symbol('id')
unique key — ES6
bigint
9007199254740991n
large integers — ES2020
object
{}, [], function, null*
reference type
INTERACTIVE — typeof explorer
console output
variables.js
// ── DECLARATIONS ──────────────────────────────────
var   x = 10;   // function-scoped, hoisted (legacy)
let   y = 20;   // block-scoped, reassignable
const z = 30;   // block-scoped, NOT reassignable (prefer this)

// ── TYPE COERCION ─────────────────────────────────
typeof "hello"        // "string"
typeof 42             // "number"
typeof null           // "object" ← famous bug!
typeof undefined      // "undefined"
typeof []             // "object" — use Array.isArray()
typeof function(){}   // "function"

// ── TRUTHY / FALSY ────────────────────────────────
// Falsy: false, 0, "", null, undefined, NaN, 0n
// Everything else is truthy
if ("")      console.log("never");
if ("hello") console.log("runs!");

// ── TYPE CONVERSION ───────────────────────────────
Number("42")           // 42
String(99)             // "99"
Boolean(0)             // false
parseInt("12.9px")     // 12
parseFloat("3.14abc") // 3.14

// ── TEMPLATE LITERALS ────────────────────────────
const name = "World";
const msg  = `Hello, ${name}! 2 + 2 = ${2+2}`;

// Tagged template
const highlight = (strings, ...vals) =>
  strings.reduce((acc, s, i) =>
    acc + s + (`[${vals[i] ?? ''}]`), '');

§ 02

Scope & Hoisting

Scope determines where variables are accessible. JavaScript uses lexical scoping — a function can access variables from where it was defined, not where it is called.

VISUAL — Scope Nesting
GLOBAL SCOPE
var globalVar const PI = 3.14

FUNCTION SCOPE
var funcVar let funcLet const funcConst

BLOCK SCOPE { }
let blockLet ✓ const blockConst ✓ var leaks out ⚠

✓ Can read: globalVar, PI, funcVar, funcLet, funcConst
✗ Cannot access: variables from other functions
scope-hoisting.js
// ── HOISTING ──────────────────────────────────────
// var is hoisted AND initialized to undefined
console.log(a); // undefined (not a ReferenceError)
var a = 5;

// let/const are hoisted but NOT initialized (TDZ)
// console.log(b);  ← ReferenceError: Cannot access before init
let b = 10;

// Function declarations are fully hoisted
greet();  // Works! "Hello"
function greet() { console.log("Hello"); }

// Function expressions are NOT hoisted
// sayHi(); ← TypeError: sayHi is not a function
const sayHi = () => console.log("Hi");

// ── SCOPE EXAMPLE ────────────────────────────────
let count = 0;                  // global
function increment() {
  count++;                       // accesses global
  let local = "I'm local";       // only inside
}
// console.log(local); ← ReferenceError

// ── var LEAKING from blocks ───────────────────────
for (var i = 0; i < 3; i++) {}
console.log(i);   // 3 — leaked out!

for (let j = 0; j < 3; j++) {}
// console.log(j); ← ReferenceError — contained

§ 03

Operators

JavaScript's operators go well beyond arithmetic — nullish coalescing, optional chaining, and logical assignment are indispensable modern tools.

INTERACTIVE — Operator Explorer
output
operators.js
// ── EQUALITY ──────────────────────────────────────
5  ==  "5"     // true  — coerces types
5  === "5"     // false — strict: same type required
null ==  undefined  // true (special rule)
null === undefined  // false

// ── NULLISH COALESCING (??) ───────────────────────
const name = null ?? "Guest";  // "Guest"
const val  = 0    ?? "default"; // 0 — 0 is NOT null/undefined
const bad  = 0    || "default"; // "default" — 0 is falsy (bug prone!)

// ── OPTIONAL CHAINING (?.) ────────────────────────
const user = { profile: { city: "Atlanta" } };
user?.profile?.city           // "Atlanta"
user?.address?.zip            // undefined (no error!)
user?.getAge?.()              // undefined (not a function, no error)
user?.scores?.[0]             // undefined

// ── LOGICAL ASSIGNMENT ────────────────────────────
let a = null;
a ??= "hello";    // a = "hello" (only if null/undefined)

let b = 5;
b &&= 10;         // b = 10 (only if b is truthy)

let c = 0;
c ||= 99;         // c = 99 (only if c is falsy)

// ── SPREAD / REST ─────────────────────────────────
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];    // [1,2,3,4,5]
const obj2 = { ...user, age: 30 }; // shallow copy + extend

function sum(...nums) {            // rest: gathers remaining
  return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4);   // 10

§ 04

Control Flow

Conditionals, loops, and branching. switch with fall-through, labeled breaks, and the compact ternary/logical patterns used in real code.

if-switch.js
// Ternary
const grade = score >= 90
  ? "A" : score >= 80
  ? "B" : "C";

// Switch with fall-through
switch (day) {
  case "Sat":
  case "Sun":
    relax(); break; // both hit this
  default:
    work();
}

// Short-circuit evaluation
isLoggedIn && showDashboard();
user || redirectToLogin();
loops.js
// for..of — iterates values
for (const item of [1,2,3]) {
  console.log(item);
}

// for..in — iterates keys
for (const key in { a:1, b:2 }) {
  console.log(key); // "a", "b"
}

// while / do-while
let n = 3;
while (n > 0) n--;

do {
  prompt();
} while (!valid);   // runs at least once

// break / continue / labels
outer: for (...) {
  for (...) {
    break outer;    // exits outer loop
  }
}

§ 05

Functions

First-class citizens in JS — passed as arguments, returned from other functions, stored in variables. Arrow functions, IIFE, recursion, currying, memoization.

INTERACTIVE — Function Demos
output
functions.js
// ── 4 WAYS TO WRITE A FUNCTION ────────────────────
function declaration(x) { return x * 2; }  // hoisted

const expression = function(x) { return x * 2; };

const arrow = (x) => x * 2;   // no own 'this'

const obj = {
  method(x) { return x * 2; } // shorthand method
};

// ── DEFAULT PARAMETERS ────────────────────────────
function greet(name = "World", greeting = "Hello") {
  return `${greeting}, ${name}!`;
}

// ── HIGHER-ORDER FUNCTIONS ────────────────────────
const double    = x => x * 2;
const applyTwice = (fn, x) => fn(fn(x)); // fn as argument
applyTwice(double, 3); // 12

// ── CURRYING ─────────────────────────────────────
const add  = a => b => a + b;
const add5 = add(5);   // partial application
add5(3);              // 8

// ── MEMOIZATION ──────────────────────────────────
function memoize(fn) {
  const cache = new Map();
  return (...args) => {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
}

// ── IIFE ─────────────────────────────────────────
const result = (function() {
  return "immediately invoked!";
})();

§ 06

Array Methods

JavaScript arrays have over 30 built-in methods. The functional trio — map, filter, reduce — forms the backbone of modern data transformation.

INTERACTIVE — Array Methods Pipeline
source array
result
array-methods.js
const nums = [1, 2, 3, 4, 5, 6, 7, 8];

// ── TRANSFORM ─────────────────────────────────────
nums.map(x => x ** 2)           // [1,4,9,16,25,36,49,64]
nums.filter(x => x % 2 === 0) // [2,4,6,8]
nums.reduce((acc, x) => acc + x, 0) // 36

// ── SEARCH ────────────────────────────────────────
nums.find(x => x > 5)          // 6
nums.findIndex(x => x > 5)   // 5
nums.some(x => x > 7)          // true
nums.every(x => x > 0)         // true
nums.includes(4)               // true

// ── MUTATE ────────────────────────────────────────
nums.push(9);                    // add to end
nums.pop();                      // remove from end
nums.unshift(0);                // add to start
nums.shift();                    // remove from start
nums.splice(2, 1, 99);           // remove 1 at idx 2, insert 99
nums.reverse();                  // reverses in place!
nums.sort((a, b) => a - b);    // numeric sort

// ── NON-MUTATING ─────────────────────────────────
nums.slice(1, 4);               // [2,3,4] — no mutation
[...new Set(nums)];              // deduplicate
nums.flat(Infinity);            // flatten nested arrays
nums.flatMap(x => [x, x*2]);  // map + flatten 1 level

// ── CHAINING ─────────────────────────────────────
[1,2,3,4,5,6]
  .filter(x => x % 2 === 0)   // [2,4,6]
  .map(x => x ** 2)            // [4,16,36]
  .reduce((a, b) => a + b);   // 56

// ── ARRAY.FROM / FILL / OF ────────────────────────
Array.from({length:5}, (_, i) => i+1); // [1,2,3,4,5]
new Array(5).fill(0);                  // [0,0,0,0,0]

§ 07

Objects & Methods

Objects are the heart of JavaScript. Understand property descriptors, getters/setters, Object utility methods, and the this keyword.

INTERACTIVE — Object Utilities
output
objects.js
// ── OBJECT CREATION ───────────────────────────────
const user = {
  name: "Alice",
  age: 30,
  greet() { return `Hi, I'm ${this.name}`; },   // method
  get info() { return `${this.name}, ${this.age}`; }, // getter
  set info(v) { [this.name, this.age] = v.split(","); }  // setter
};

// ── COMPUTED / DYNAMIC KEYS ───────────────────────
const key  = "score";
const data = { [key]: 100, [`${key}_max`]: 200 };

// ── Object METHODS ────────────────────────────────
Object.keys(user)      // ["name","age"]
Object.values(user)    // ["Alice", 30]
Object.entries(user)   // [["name","Alice"],["age",30]]
Object.fromEntries(entries) // reverse of entries
Object.assign({}, user, { age: 31 })  // shallow merge
Object.freeze(user)    // no more changes

// ── DEEP CLONE ────────────────────────────────────
const deep = structuredClone(user);  // ES2022 ✓
const old  = JSON.parse(JSON.stringify(user)); // classic

// ── THIS KEYWORD ─────────────────────────────────
const obj = { val: 42, getVal() { return this.val; } };
const bound = obj.getVal.bind(obj);  // lock 'this'
obj.getVal.call({ val: 99 });         // temporary this
obj.getVal.apply(obj, []);            // same but array args

§ 08

Map & Set

Map allows any type as key (not just strings). Set stores unique values. Both are iterable and maintain insertion order.

Map — key/value store
Key (any type)Value
"name""Alice"
42 (number)"answer"
true (bool)[1,2,3]
{id:1} (object)"user obj key!"
Set — unique values only
Values (duplicates removed)Has?
1, 2, 3✓ deduplicated
"apple", "banana"✓ unique strings
NaN✓ only once
{} vs {}✗ different refs
INTERACTIVE
output

§ 09

Destructuring

Unpack arrays and objects into variables in a single expression. The most used ES6 feature in real codebases.

array-destructure.js
const [a, b, c]   = [1, 2, 3];
const [x, , z]    = [1, 2, 3]; // skip middle
const [head, ...tail] = [1,2,3,4];
// head=1, tail=[2,3,4]

// Swap variables — no temp needed!
let p = 1, q = 2;
[p, q] = [q, p];   // p=2, q=1

// Default values
const [m = 10, n = 20] = [99];
// m=99, n=20

// From function return
function range() { return [1, 100]; }
const [min, max] = range();
object-destructure.js
const user = { name:"Bob", age:25, city:"LA" };

// Basic
const { name, age } = user;

// Rename
const { name: userName } = user;

// Default values
const { role = "user" } = user;

// Rest — collect remainder
const { city, ...rest } = user;
// rest = { name:"Bob", age:25 }

// Nested
const { address: { zip } } =
  { address: { zip: "30024" } };

// Function parameters
function display({ name, age = 0 }) {
  return `${name} is ${age}`;
}

§ 10

Closures

A closure is a function that remembers the variables from its outer scope even after that scope has finished executing.

INTERACTIVE — Counter using Closure
0
Each button calls a method on the same closure — the internal count variable is private, shared only through the returned API.
VISUAL — Closure Memory
let count = 0
increment() decrement() reset()
↑ All 3 functions close over the same count variable
closures.js
// ── BASIC CLOSURE ─────────────────────────────────
function makeCounter() {
  let count = 0;                   // private state
  return {
    increment: () => ++count,
    decrement: () => --count,
    reset:     () => count = 0,
    value:     () => count
  };
}
const counter = makeCounter();
counter.increment();   // 1
counter.increment();   // 2
counter.value();        // 2

// ── CLOSURE FOR MODULE PATTERN ────────────────────
const wallet = (function() {
  let balance = 0;
  return {
    deposit: n  => balance += n,
    withdraw: n => {
      if (n > balance) throw new Error("Insufficient funds");
      balance -= n;
    },
    getBalance: () => balance
  };
})();

// ── CLOSURE PITFALL (var in loops) ────────────────
// Bug: all callbacks see the same 'i' (after loop ends)
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0); // prints 3,3,3
}

// Fix: use let (block-scoped per iteration)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0); // prints 0,1,2 ✓
}

§ 11

Classes & OOP

ES6 classes are syntactic sugar over prototype chains. Private fields (#), static methods, inheritance, and mixins give you full OOP power.

VISUAL — Class Inheritance
Animal
constructor(name) · speak() · toString()
Dog extends Animal
fetch() · speak() [override]
Cat extends Animal
purr() · speak() [override]
INTERACTIVE — Classes
output
classes.js
class Animal {
  #name;                             // private field (ES2022)
  static count = 0;                  // static class field

  constructor(name) {
    this.#name = name;
    Animal.count++;
  }

  speak()  { return `${this.#name} makes a noise`; }
  get  name() { return this.#name; }
  set  name(v) { this.#name = v; }

  static create(n) { return new Animal(n); }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);               // MUST call super first
    this.breed = breed;
  }
  speak() {
    return `${super.speak()} — Woof!`;
  }
  fetch(item) { return `${this.name} fetches ${item}`; }
}

// Mixin — share behavior without inheritance
const Serializable = (Base) => class extends Base {
  serialize()   { return JSON.stringify(this); }
  static deserialize(s) { return JSON.parse(s); }
};
class SerializableDog extends Serializable(Dog) {}

§ 12

The Prototype Chain

Every JavaScript object has a hidden [[Prototype]] link. Property lookups traverse this chain until null is reached.

prototypes.js
// Object.create — explicit prototype
const animal = {
  describe() { return `I am ${this.name}`; }
};
const dog = Object.create(animal);
dog.name = "Rex";
dog.describe();   // "I am Rex" — found on prototype

// Prototype chain lookup order
// dog → animal → Object.prototype → null

// Check prototype membership
dog instanceof Object           // true
Object.getPrototypeOf(dog) === animal // true

// hasOwnProperty vs inherited
dog.hasOwnProperty("name")     // true — own
dog.hasOwnProperty("describe") // false — on prototype

// Prototype augmentation (use sparingly!)
Array.prototype.sum = function() {
  return this.reduce((a, b) => a + b, 0);
};
[1, 2, 3].sum();   // 6

§ 13

Iterators & Generators

The iterator protocol powers for...of, spread, and destructuring. Generators are pausable functions that produce sequences on demand.

INTERACTIVE — Generators
output
generators.js
// ── GENERATOR FUNCTION ────────────────────────────
function* range(start, end) {
  for (let i = start; i <= end; i++) {
    yield i;          // pauses, returns i
  }
}

for (const n of range(1, 5)) {
  console.log(n);     // 1,2,3,4,5
}

// ── INFINITE GENERATOR ───────────────────────────
function* ids() {
  let id = 1;
  while (true) yield id++;
}
const gen = ids();
gen.next().value;  // 1
gen.next().value;  // 2
gen.next().value;  // 3 — can go forever

// ── CUSTOM ITERABLE ───────────────────────────────
const range2 = {
  [Symbol.iterator]() {
    let i = 0;
    return {
      next: () => i < 5
        ? { value: i++, done: false }
        : { value: undefined, done: true }
    };
  }
};
[...range2];  // [0,1,2,3,4]

// ── yield* DELEGATION ────────────────────────────
function* concat(...iters) {
  for (const it of iters) yield* it;
}

§ 14

Promises & Async/Await

Asynchronous code that reads like synchronous. Understand the event loop, microtask queue, and how async/await desugars to Promise chains.

VISUAL — Promise States
new Promise()PENDING
.then()FULFILLED ✓
.then()CHAINED ✓
or
throw / reject()REJECTED ✗
.catch()CAUGHT ✓
INTERACTIVE — Async Demos
output (watch timing)
async-await.js
// ── PROMISE ───────────────────────────────────────
const delay = (ms) =>
  new Promise(resolve => setTimeout(resolve, ms));

// Chaining
fetchUser(1)
  .then(user   => fetchPosts(user.id))
  .then(posts  => render(posts))
  .catch(err   => console.error(err))
  .finally(()  => hideSpinner());

// ── ASYNC / AWAIT ─────────────────────────────────
async function loadData() {
  try {
    const user  = await fetchUser(1);   // pauses here
    const posts = await fetchPosts(user.id);
    return posts;
  } catch (err) {
    console.error("Failed:", err.message);
  }
}

// ── PARALLEL REQUESTS ─────────────────────────────
// Sequential (slow): each waits for prev
const a = await fetch1();
const b = await fetch2();

// Parallel (fast): both fire simultaneously
const [a, b] = await Promise.all([fetch1(), fetch2()]);

// ── PROMISE UTILITIES ─────────────────────────────
Promise.all([p1, p2, p3])       // all must resolve
Promise.allSettled([p1, p2])    // waits for all, captures rejections
Promise.race([p1, p2])          // first to settle wins
Promise.any([p1, p2])           // first to RESOLVE wins (ignores rejects)
Promise.resolve(42)             // instantly resolved

§ 15

Fetch & APIs

The Fetch API is the modern way to make HTTP requests. Combine with async/await for clean, readable network code.

INTERACTIVE — Live Fetch Demo
live api response (jsonplaceholder.typicode.com)
Click a button to make a real HTTP request...
fetch-api.js
// ── GET ───────────────────────────────────────────
async function getUser(id) {
  const res  = await fetch(`https://api.example.com/users/${id}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();                   // parses JSON body
}

// ── POST ──────────────────────────────────────────
async function createPost(data) {
  const res = await fetch("/api/posts", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data)
  });
  return res.json();
}

// ── ABORT CONTROLLER ─────────────────────────────
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);

await fetch(url, { signal: controller.signal });
clearTimeout(timer);

// ── STREAMING RESPONSE ────────────────────────────
const res    = await fetch(url);
const reader = res.body.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  processChunk(value);
}

§ 16

DOM Manipulation

Create, read, update, and delete elements in the page. The DOM is a live tree — every change immediately reflects in the browser.

INTERACTIVE — Live DOM Builder
DOM stage — elements rendered here
DOM nodes will appear here...
dom-manipulation.js
// ── SELECTING ─────────────────────────────────────
document.querySelector('.card')           // first match
document.querySelectorAll('li')          // NodeList
document.getElementById('main')         // by id
document.getElementsByClassName('btn')  // HTMLCollection
el.closest('.container')               // walk UP the tree
el.matches(':hover')                    // test selector

// ── CREATING ──────────────────────────────────────
const div = document.createElement('div');
div.textContent = 'New Element';
div.className   = 'card amber';
div.setAttribute('data-id', '42');
div.dataset.id  = '42';              // same via dataset
parent.append(div);                   // add at end
parent.prepend(div);                  // add at start
parent.insertBefore(div, sibling);    // insert before
div.after(other);                     // after this element

// ── STYLING ───────────────────────────────────────
el.style.color           = 'red';
el.style.cssText         = 'color:red; font-size:16px';
el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('open');          // add if absent
el.classList.replace('old', 'new');
el.classList.contains('active');     // boolean
el.style.setProperty('--color', 'red'); // CSS vars!

// ── SAFE HTML ─────────────────────────────────────
el.textContent  = '<b>safe</b>';         // escaped, safe
el.innerHTML    = '<b>rendered</b>';      // ⚠ XSS risk!
el.insertAdjacentHTML('beforeend', html); // efficient insert

§ 17

Events & Delegation

Events bubble from target to root. Event delegation attaches one listener to a parent, handling clicks on all children — even future ones.

INTERACTIVE — Event Bubbling
Click Me — watch the bubble!
events.js
// ── ADD / REMOVE LISTENERS ────────────────────────
const handler = (e) => console.log(e.target);
el.addEventListener('click', handler);
el.removeEventListener('click', handler);   // must be same reference!

// Once option
el.addEventListener('click', handler, { once: true });

// ── EVENT OBJECT ─────────────────────────────────
el.addEventListener('click', (e) => {
  e.target           // element clicked
  e.currentTarget    // element with listener
  e.type             // "click"
  e.preventDefault() // stop default (link, form submit)
  e.stopPropagation()// stop bubbling
  e.clientX, e.clientY   // mouse position
  e.key, e.code      // keyboard
});

// ── EVENT DELEGATION ─────────────────────────────
// One listener handles ALL current and future children
document.querySelector('#list')
  .addEventListener('click', (e) => {
    if (e.target.matches('li')) {
      handleItem(e.target.dataset.id);
    }
  });

// ── CUSTOM EVENTS ────────────────────────────────
const evt = new CustomEvent('userLogin', {
  detail: { user: "Alice" },
  bubbles: true
});
document.dispatchEvent(evt);

// ── INTERSECTION OBSERVER ─────────────────────────
const obs = new IntersectionObserver((entries) => {
  entries.forEach(e => e.target.classList.toggle('visible', e.isIntersecting));
}, { threshold: 0.2 });
obs.observe(el);

§ 18

Web Storage

localStorage persists after closing the browser. sessionStorage clears when the tab closes. Both are synchronous key-value stores limited to ~5MB of strings.

INTERACTIVE — localStorage
stored items
web-storage.js
// ── localStorage (persists) ────────────────────────
localStorage.setItem('key', 'value');
localStorage.getItem('key');          // "value"
localStorage.removeItem('key');
localStorage.clear();

// ⚠ Can only store strings! Serialize objects:
localStorage.setItem('user', JSON.stringify({ name:"Alice" }));
const user = JSON.parse(localStorage.getItem('user'));

// ── sessionStorage (tab only) ──────────────────────
sessionStorage.setItem('token', 'abc123');

// ── Iterate all keys ──────────────────────────────
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  console.log(key, localStorage.getItem(key));
}

// ── storage event (cross-tab communication) ───────
window.addEventListener('storage', (e) => {
  console.log(e.key, e.oldValue, e.newValue);
});

// ── IndexedDB (structured, large data) ────────────
// For storing > 5MB or complex objects, use IndexedDB
// or libraries like idb / Dexie.js

// ── Cookie vs localStorage ────────────────────────
// Cookies: sent with every HTTP request, expire, small
// localStorage: client-only, no expiry, ~5MB
// sessionStorage: cleared on tab close, ~5MB

§ 19

Regular Expressions

A mini-language for pattern matching. Use .test() for boolean checks, .match()/.exec() for extraction, .replace() for transformation.

INTERACTIVE — Live Regex Tester
matches (highlighted)
regex.js
// ── CREATING ──────────────────────────────────────
const re1 = /\d+/g;            // literal
const re2 = new RegExp('\\d+', 'g'); // dynamic

// ── METHODS ───────────────────────────────────────
/hello/.test("hello world")     // true
"12px".match(/\d+/)            // ["12"]
"a1b2".matchAll(/[a-z](\d)/g)  // iterator of all matches+groups
"hello".replace(/l/g, 'r')     // "herro"
"a,b,,c".split(/,+/)           // ["a","b","c"]
/\d+/.exec("abc123")            // match object

// ── USEFUL PATTERNS ──────────────────────────────
/^[\w.-]+@[\w.-]+\.\w{2,}$/    // email
/^\+?[\d\s\-()]{7,15}$/        // phone
/^https?:\/\/[^\s]+$/          // url
/#[0-9A-Fa-f]{3,6}\b/g         // hex color
/\b\d{4}-\d{2}-\d{2}\b/        // date YYYY-MM-DD
/(?<=\$)\d+(\.\d{2})?/g       // price after $

// ── GROUPS ────────────────────────────────────────
const m = "2025-01-15".match(/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/);
m.groups.y   // "2025" — named group

// ── replaceAll with function ──────────────────────
"a1 b2 c3".replace(/(\w)(\d)/g,
  (match, letter, num) => `${letter.toUpperCase()}${Number(num)*10}`
); // "A10 B20 C30"

§ 20

Modern ES6+

A tour of every major feature added since ES6 (2015) through ES2024 — the features you'll see in every modern codebase.

INTERACTIVE — Modern JS Features
output
modern-js.js
// ── PROXY (ES6) ───────────────────────────────────
const handler = {
  get(target, prop) {
    return prop in target ? target[prop] : `${prop} not found`;
  },
  set(target, prop, val) {
    if (typeof val !== "number") throw TypeError("Must be number");
    target[prop] = val; return true;
  }
};
const p = new Proxy({}, handler);

// ── SYMBOL (ES6) ─────────────────────────────────
const id = Symbol('id');
const obj = { [id]: 42 };               // non-enumerable key
Symbol.for('shared')                    // global registry

// ── Array.at() (ES2022) ───────────────────────────
const arr = [1,2,3,4,5];
arr.at(-1)    // 5 — last element
arr.at(-2)    // 4

// ── Object.hasOwn (ES2022) ────────────────────────
Object.hasOwn(obj, 'name')   // better than hasOwnProperty

// ── structuredClone (ES2022) ──────────────────────
const deep = structuredClone({ a: { b: 1 } });

// ── Array.from { groupBy } (ES2024) ───────────────
const items = [
  { type:'fruit', name:'apple'  },
  { type:'veg',   name:'carrot' },
  { type:'fruit', name:'banana' }
];
Object.groupBy(items, item => item.type);
// { fruit:[apple,banana], veg:[carrot] }

// ── Temporal (ES2024) — better dates ──────────────
const now = Temporal.Now.plainDateTimeISO();
now.add({ days: 30 });

// ── Logical Assignment (ES2021) ───────────────────
x ??= "default";   // x = x ?? "default"
x ||= 0;           // x = x || 0
x &&= 1;           // x = x && 1

§ 21

Error Handling

Robust error handling separates production code from tutorials. Custom error types, error boundaries, and safe async patterns.

INTERACTIVE
output
error-handling.js
// ── TRY / CATCH / FINALLY ─────────────────────────
try {
  const data = JSON.parse(invalidJSON);
} catch (e) {
  if (e instanceof SyntaxError) {
    console.error("Bad JSON:", e.message);
  } else throw e;  // re-throw unknown errors
} finally {
  closeConnection();  // always runs
}

// ── CUSTOM ERROR CLASSES ──────────────────────────
class ValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name  = 'ValidationError';
    this.field = field;
  }
}
class NetworkError extends Error {
  constructor(status, url) {
    super(`HTTP ${status}`);
    this.name   = 'NetworkError';
    this.status = status;
    this.url    = url;
  }
}

// ── ASYNC ERROR PATTERNS ─────────────────────────
async function safeFetch(url) {
  const res = await fetch(url);
  if (!res.ok) throw new NetworkError(res.status, url);
  return res.json();
}

// ── Result pattern (no throws) ────────────────────
async function safeRun(fn) {
  try        { return [null, await fn()]; }
  catch (e)  { return [e, null]; }
}
const [err, data] = await safeRun(() => fetchData());
if (err) handleError(err);

§ 22

Design Patterns

Proven solutions to recurring problems. These patterns appear constantly in real JavaScript codebases.

INTERACTIVE — Pattern Demos
output
patterns.js
// ── SINGLETON ─────────────────────────────────────
const Store = (function() {
  let instance;
  return {
    getInstance() {
      if (!instance) instance = { state: {}, version: 1 };
      return instance;
    }
  };
})();

// ── OBSERVER / PubSub ─────────────────────────────
class EventBus {
  #listeners = {};
  on(event, cb) {
    (this.#listeners[event] ??= []).push(cb);
  }
  off(event, cb) {
    this.#listeners[event] =
      (this.#listeners[event] ?? []).filter(l => l !== cb);
  }
  emit(event, data) {
    (this.#listeners[event] ?? []).forEach(cb => cb(data));
  }
}

// ── FUNCTION COMPOSITION ─────────────────────────
const pipe = (...fns) =>
  (x) => fns.reduce((v, f) => f(v), x);

const process = pipe(
  x => x.trim(),
  x => x.toLowerCase(),
  x => x.replace(/\s+/g, '-')
);
process("  Hello World  ");  // "hello-world"

// ── DECORATOR PATTERN ─────────────────────────────
function withLogging(fn) {
  return function(...args) {
    console.log(`Calling ${fn.name} with`, args);
    const result = fn.apply(this, args);
    console.log(`Result:`, result);
    return result;
  };
}
const loggedAdd = withLogging((a, b) => a + b);