JavaScript ES6+ Reference
JavaScript ES6+ Reference
The modern JavaScript patterns you actually use: destructuring, async/await, modules, classes, optional chaining, and the array/object methods that replace most loops.
Variables — const, let, and why var is done
// const — block-scoped, must be initialised, cannot be reassigned
const API_URL = "https://api.example.com";
const config = { timeout: 5000 };
config.timeout = 10000; // FINE — mutating the object, not reassigning the binding
// config = {}; // TypeError — cannot reassign const
// let — block-scoped, can be reassigned
let count = 0;
count += 1;
// Temporal dead zone — let/const are hoisted but NOT initialised
// console.log(x); // ReferenceError
let x = 1;
// var problems (avoid)
// 1. Function-scoped, not block-scoped — leaks out of if/for blocks
// 2. Hoisted AND initialised to undefined — silent bugs
// 3. Can be re-declared in same scope
for (var i = 0; i < 3; i++) {}
console.log(i); // 3 — leaked!
for (let j = 0; j < 3; j++) {}
// console.log(j); // ReferenceError — correct
// Rule: default to const. Use let when you need to reassign. Never use var.
Destructuring — arrays and objects
// Object destructuring
const { name, age, city = "London" } = user; // default value
const { name: fullName } = user; // rename to fullName
const { address: { street } } = user; // nested
const { name, ...rest } = user; // rest collects remaining keys
// Array destructuring
const [first, second, , fourth] = arr; // skip index 2
const [head, ...tail] = arr; // rest in arrays
const [a, b] = [b, a]; // swap values (classic)
// Function parameter destructuring
function greet({ name, role = "user" }) {
return `Hello ${name} (${role})`;
}
greet({ name: "Alice", role: "admin" });
// Destructure in loops
const users = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
for (const { id, name } of users) {
console.log(id, name);
}
// Destructure fetch response
const { data, status } = await fetchUser(id);
// Rename + default + nested — common in React/API code
const {
user: {
profile: { avatar = "/default.png", displayName: name = "Anonymous" } = {}
} = {}
} = apiResponse;
Spread and rest operators
// Spread — expand iterable into individual elements
const merged = { ...defaults, ...overrides }; // object merge (right wins)
const copy = { ...original }; // shallow copy
const arr2 = [...arr1, 4, 5]; // array concat
const args = [...arguments]; // arguments → real array
// Spread for immutable updates (React/Redux pattern)
const updated = { ...state, count: state.count + 1 };
const newList = [...list, newItem];
const removed = list.filter(item => item.id !== id); // no spread needed
// Spread with function calls
Math.max(...numbers);
console.log(...items);
new Date(...dateParts);
// Rest parameters — collect remaining args into array
function sum(...nums) {
return nums.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3, 4); // 10
function first(a, b, ...rest) {
console.log(a, b, rest); // rest is a real array
}
// Rest in destructuring
const { id, ...profile } = user; // profile has everything except id
const [head, ...tail] = [1, 2, 3, 4]; // tail = [2, 3, 4]
Template literals and tagged templates
// Template literals — backtick strings
const greeting = `Hello, ${name}!`;
const multiline = `
SELECT *
FROM users
WHERE id = ${userId}
`;
// Expressions in templates
const msg = `${items.length} item${items.length !== 1 ? "s" : ""}`;
const price = `Total: ${(qty * unitPrice).toFixed(2)}`;
const path = `${base}/${endpoint.replace(/^\//, "")}`;
// Nested template literals
const html = `${items.map(i => `- ${i.name}
`).join("")}
`;
// Tagged templates — tag function receives split strings + values
function sql(strings, ...values) {
return {
text: strings.reduce((q, s, i) => q + s + (i < values.length ? `$${i + 1}` : ""), ""),
values
};
}
const query = sql`SELECT * FROM users WHERE id = ${userId} AND active = ${true}`;
// { text: "SELECT * FROM users WHERE id = $1 AND active = $2", values: [1, true] }
// String.raw — no escape processing
String.raw`C:\Users\${name}\Documents` // backslashes preserved
Arrow functions and this binding
// Arrow function syntax
const double = x => x * 2;
const add = (a, b) => a + b;
const greet = name => ({ message: `Hello ${name}` }); // wrap object in ()
const noop = () => {};
// Multi-line body requires explicit return
const process = (items) => {
const filtered = items.filter(i => i.active);
return filtered.map(i => i.name);
};
// Key difference: arrow functions do NOT have their own `this`
// They capture `this` from the surrounding lexical scope
class Timer {
constructor() {
this.seconds = 0;
}
start() {
// Arrow: `this` is the Timer instance
setInterval(() => this.seconds++, 1000);
// Regular function: `this` would be undefined (strict) or global
// setInterval(function() { this.seconds++; }, 1000); // BROKEN
}
}
// Common patterns
const bound = array
.filter(item => item.active)
.map(item => item.name)
.sort((a, b) => a.localeCompare(b));
// Arrow functions have no `arguments` — use rest params
const logAll = (...args) => console.log(...args);
// Arrow functions cannot be constructors
// const Obj = () => {}; new Obj(); // TypeError
Async/await and Promises
// Promise basics
fetch("/api/user")
.then(res => res.json())
.then(user => console.log(user))
.catch(err => console.error(err))
.finally(() => setLoading(false));
// async/await — cleaner sequential async
async function loadUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const user = await res.json();
return user;
} catch (err) {
console.error("Failed to load user:", err);
throw err; // re-throw so callers can handle it
}
}
// Parallel execution — don't await one by one if independent
// SLOW — sequential:
const user = await fetchUser(id);
const posts = await fetchPosts(id);
// FAST — parallel:
const [user, posts] = await Promise.all([fetchUser(id), fetchPosts(id)]);
// Promise.allSettled — get all results even if some fail
const results = await Promise.allSettled([fetchA(), fetchB(), fetchC()]);
for (const result of results) {
if (result.status === "fulfilled") console.log(result.value);
else console.error(result.reason);
}
// Promise.race — first to resolve/reject wins
const data = await Promise.race([fetch("/api/data"), timeout(5000)]);
// Promise.any — first to resolve (ignores rejections until all fail)
const fastest = await Promise.any([fetchPrimary(), fetchFallback()]);
// async IIFE — top-level await alternative (pre-ESM)
(async () => {
const data = await loadData();
console.log(data);
})();
// Top-level await (ESM modules only)
const config = await fetchConfig(); // valid at module top level
Common mistake: await inside .map() doesn't work — you get an array of Promises. Use await Promise.all(arr.map(async item => ...)).
Array methods — the ones that replace most loops
const users = [
{ id: 1, name: "Alice", role: "admin", active: true, score: 95 },
{ id: 2, name: "Bob", role: "user", active: false, score: 72 },
{ id: 3, name: "Carol", role: "user", active: true, score: 88 },
];
// map — transform each element, returns new array
const names = users.map(u => u.name); // ["Alice", "Bob", "Carol"]
const cards = users.map(u => ({ ...u, label: `#${u.id}` }));
// filter — keep elements matching predicate
const active = users.filter(u => u.active);
const admins = users.filter(u => u.role === "admin");
// find / findIndex — first match
const alice = users.find(u => u.name === "Alice");
const idx = users.findIndex(u => u.id === 3); // 2
// reduce — aggregate to single value
const total = users.reduce((sum, u) => sum + u.score, 0); // 255
const byId = users.reduce((map, u) => ({ ...map, [u.id]: u }), {});
// { 1: Alice, 2: Bob, 3: Carol }
// some / every — boolean checks
const anyAdmin = users.some(u => u.role === "admin"); // true
const allActive = users.every(u => u.active); // false
// flat / flatMap
const nested = [[1, 2], [3, 4]];
const flat = nested.flat(); // [1, 2, 3, 4]
const sentences = ["hello world", "foo bar"];
const words = sentences.flatMap(s => s.split(" ")); // ["hello","world","foo","bar"]
// sort — mutates! copy first
const sorted = [...users].sort((a, b) => b.score - a.score); // descending
const alpha = [...users].sort((a, b) => a.name.localeCompare(b.name));
// includes / indexOf
[1, 2, 3].includes(2); // true
["a", "b"].indexOf("b"); // 1
// Array.from — create array from iterable or array-like
Array.from({ length: 5 }, (_, i) => i); // [0, 1, 2, 3, 4]
Array.from(new Set([1, 1, 2, 2, 3])); // [1, 2, 3] — dedup
Array.from(document.querySelectorAll("p")); // NodeList → Array
// at() — negative indexing (ES2022)
arr.at(-1); // last element
arr.at(-2); // second to last
Objects — modern patterns
// Shorthand properties and methods
const name = "Alice", age = 30;
const user = { name, age }; // { name: "Alice", age: 30 }
const obj = {
value: 42,
getValue() { return this.value; }, // method shorthand
get doubled() { return this.value * 2; }, // getter
set doubled(v) { this.value = v / 2; }, // setter
[computedKey]: "dynamic", // computed property name
};
// Object.keys / values / entries
Object.keys(obj) // ["a", "b", ...]
Object.values(obj) // [1, 2, ...]
Object.entries(obj) // [["a", 1], ["b", 2], ...]
// Rebuild object from entries (transform values)
Object.fromEntries(
Object.entries(config).filter(([, v]) => v !== null)
);
// Object.assign — shallow merge (prefer spread)
const merged = Object.assign({}, defaults, overrides);
const copy = Object.assign({}, obj);
// Object.freeze — immutable (shallow)
const CONSTANTS = Object.freeze({ MAX: 100, MIN: 0 });
// Nullish coalescing (??) — only for null/undefined
const port = config.port ?? 3000; // 0 is valid — ?? won't override it
// vs:
const port2 = config.port || 3000; // 0 would be replaced — usually wrong
// Optional chaining (?.)
const city = user?.address?.city; // undefined if any part is null/undefined
const first = arr?.[0]; // safe array access
const result = obj?.method?.(); // safe method call
// Logical assignment
x ??= "default"; // assign only if x is null/undefined
x ||= "fallback"; // assign only if x is falsy
x &&= transform(x); // assign only if x is truthy
Classes
class Animal {
// Private fields (ES2022) — enforced at engine level, not just convention
#name;
#sound;
static count = 0; // static field
constructor(name, sound) {
this.#name = name;
this.#sound = sound;
Animal.count++;
}
speak() {
return `${this.#name} says ${this.#sound}`;
}
get name() { return this.#name; } // getter
static create(name, sound) { // factory method
return new Animal(name, sound);
}
}
class Dog extends Animal {
#tricks = [];
constructor(name) {
super(name, "woof"); // must call super() before using this
}
learn(trick) {
this.#tricks.push(trick);
return this; // fluent interface / method chaining
}
speak() {
const base = super.speak(); // call parent method
return `${base}! (trained: ${this.#tricks.join(", ")})`;
}
}
const dog = new Dog("Rex").learn("sit").learn("stay");
dog.speak(); // "Rex says woof! (trained: sit, stay)"
// Mixins — JS has no multiple inheritance, use mixins
const Serializable = (Base) => class extends Base {
toJSON() { return JSON.stringify(this); }
static fromJSON(json) { return Object.assign(new this(), JSON.parse(json)); }
};
class User extends Serializable(Animal) {}
Modules — import / export
// Named exports — math.js
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export class Vector { constructor(x, y) { this.x = x; this.y = y; } }
// Default export — one per module
export default function main() { /* ... */ }
// Re-export (barrel file — index.js)
export { add, PI } from "./math.js";
export { default as Vector } from "./vector.js";
export * from "./utils.js";
// Imports
import main from "./main.js"; // default
import { add, PI } from "./math.js"; // named
import { add as sum } from "./math.js"; // rename
import * as math from "./math.js"; // namespace import
import main, { add } from "./module.js"; // both
// Dynamic import — lazy load (returns Promise)
const { add } = await import("./math.js");
const module = await import(`./plugins/${name}.js`);
// import.meta
console.log(import.meta.url); // URL of current module
console.log(import.meta.env); // Vite/bundler env vars
// JSON modules (Node 22+, --experimental-json-modules in older)
import data from "./data.json" assert { type: "json" };
// CommonJS (Node.js legacy — still common in older packages)
const fs = require("fs");
module.exports = { add };
// Interop: import cjs from "./legacy.cjs"; // default import of module.exports
Map, Set, WeakMap, WeakSet
// Map — key-value, any type as key, preserves insertion order
const map = new Map();
map.set("key", "value");
map.set(objKey, data); // objects as keys — unlike plain objects
map.get("key"); // "value"
map.has("key"); // true
map.delete("key");
map.size; // number of entries
// Iterate
for (const [key, value] of map) { /* ... */ }
[...map.keys()]
[...map.values()]
[...map.entries()]
// Build from array of pairs
const map2 = new Map([["a", 1], ["b", 2]]);
// Set — unique values, any type, preserves insertion order
const set = new Set([1, 2, 3, 2, 1]); // {1, 2, 3}
set.add(4);
set.has(2); // true
set.delete(1);
set.size; // 3
// Deduplication (the main use case)
const unique = [...new Set(arr)];
const uniqueById = [...new Map(users.map(u => [u.id, u])).values()];
// Set operations (ES2025 methods — polyfill may be needed)
setA.union(setB)
setA.intersection(setB)
setA.difference(setB)
// WeakMap — keys must be objects, NOT iterable, no .size
// Allows GC to collect keys when no other references exist
// Use case: attach private data to DOM nodes or class instances without memory leaks
const metadata = new WeakMap();
metadata.set(element, { clicks: 0 });
// WeakSet — objects only, NOT iterable
// Use case: track "visited" objects without preventing GC
const visited = new WeakSet();
visited.add(node);
Symbols, iterators, and generators
// Symbol — unique, immutable primitive, used for non-string keys
const id = Symbol("id");
const obj = { [id]: 123, name: "Alice" };
obj[id]; // 123
Object.keys(obj); // ["name"] — symbols are not enumerable
// Well-known symbols — hook into JS language behaviour
class Range {
constructor(start, end) { this.start = start; this.end = end; }
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
return current <= end
? { value: current++, done: false }
: { done: true };
}
};
}
}
for (const n of new Range(1, 5)) console.log(n); // 1 2 3 4 5
[...new Range(1, 3)]; // [1, 2, 3]
// Generators — functions that pause execution
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
fib.next().value; // 0
fib.next().value; // 1
fib.next().value; // 1
// Async generators — for streaming/pagination
async function* paginate(url) {
let next = url;
while (next) {
const res = await fetch(next);
const { data, nextUrl } = await res.json();
yield data;
next = nextUrl;
}
}
for await (const page of paginate("/api/items")) {
process(page);
}
Error handling patterns
// Custom error classes
class AppError extends Error {
constructor(message, code, statusCode = 500) {
super(message);
this.name = this.constructor.name;
this.code = code;
this.statusCode = statusCode;
}
}
class NotFoundError extends AppError {
constructor(resource, id) {
super(`${resource} ${id} not found`, "NOT_FOUND", 404);
}
}
// throw and instanceof
throw new NotFoundError("User", 42);
try {
await loadUser(id);
} catch (err) {
if (err instanceof NotFoundError) redirect("/404");
else if (err instanceof AppError) showError(err.message);
else throw err; // re-throw unexpected errors
}
// Error cause (ES2022) — chain errors with context
try {
await db.query(sql);
} catch (cause) {
throw new AppError("Database query failed", "DB_ERROR", 500, { cause });
}
// Catching without binding (ES2019)
try { JSON.parse(str); }
catch { return null; } // no `err` variable needed
// Promise error handling
loadUser(id)
.then(process)
.catch(err => {
if (err instanceof NotFoundError) return null;
throw err;
});
// Global unhandled rejection handler (Node.js)
process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled rejection:", reason);
process.exit(1);
});
🔍 Free tool: npm Package Health Checker — check any npm package — React, Lodash, Axios — for EOL status, known CVEs, and active maintenance in seconds.
📦 Free tool: package.json Health Checker — paste your entire package.json and get A–F health grades for all dependencies at once, including React, Vue, TypeScript, and build tools.
Founded
2023 in London, UK
Contact
hello@releaserun.com