ES6+ Cheatsheet - ES6+ JavaScript Syntax Reference

This reference is for JavaScript developers who want to write modern code instead of callback-and-var-era patterns, covering the ES2015+ features that reshaped daily use: let/const block scoping, arrow functions, destructuring, template literals, Promise/async flow, import/export modules, classes, Map/Set, and iterators. Unlike a dry spec list, entries are grouped by the situation each feature fixes — scope confusion, unwieldy callbacks, or verbose object access. After reading you should be able to replace var with block-scoped bindings, flatten nested callbacks with async/await, and structure multi-file projects with import/export.

Languages·44 commands·Last updated 2026-07-21

Typical Use Case

For developers who want to genuinely master ES6 and the syntax that followed it. This sheet is organised as variables and functions, destructuring, async programming, modules, classes and data structures, then Proxy/Reflect and generators, and it is deliberately built around the points people get wrong: const locks the binding rather than the contents, arrow functions have no this of their own, ?. and ?? short-circuit only on null and undefined, and Promise.all differs from allSettled in how failures are handled. The data-structure section contrasts Map and Set with WeakMap on key constraints and reference semantics, Proxy with Reflect for default-behaviour forwarding — the basis of Vue 3 reactivity — and the troubleshooting entries cover await that never runs, destructuring that silently loses values, and closures shared by var in a loop.

Variables & Functions 9

let x = 1
Block-scoped variable
const PI = 3.14
Block-scoped constant
const fn = (x) => x * 2
Arrow function
const fn = (x, y = 1) => x + y
Default parameter
const fn = (...args) => args
Rest parameters
fn(...[1, 2, 3])
Spread operator
user?.name
Optional chaining (short-circuits on null/undefined)
value ?? "default"
Nullish coalescing (only null/undefined fallback)
`Hello ${name}`
Template string interpolation

Destructuring 5

const [a, b] = [1, 2]
Array destructuring
const { name, age } = user
Object destructuring
const { name: userName } = user
Rename while destructuring
const [a, ...rest] = [1, 2, 3]
Destructuring with rest
function fn({ name, age = 18 }) {}
Function parameter destructuring

Asynchronous 8

new Promise((resolve, reject) => {})
Create a Promise
promise.then().catch().finally()
Chained calls
Promise.all([p1, p2])
Run all in parallel
Promise.race([p1, p2])
Take the first to finish
async function fn() { await promise }
async/await
try { await fn() } catch (e) {}
Error handling
Promise.allSettled([p1, p2])
All settled (success or fail)
Promise.any([p1, p2])
Any (first to succeed)

Modules 6

export const fn = () => {}
Named export
export default fn
Default export
import { fn } from "./module"
Named import
import fn from "./module"
Default import
import * as mod from "./module"
Import everything
import("./module").then(mod => {})
Dynamic import

Classes & Data Structures 8

class User { constructor() {} }
Class definition
class Admin extends User {}
Inheritance
const map = new Map()
Map (keys can be objects)
const set = new Set([1, 2, 3])
Set (deduplicated)
for (const [k, v] of map) {}
Iterate a Map
Array.from(set)
Set to array
Symbol("id")
Unique identifier
for (const item of iterable) {}
Iterator protocol

Proxy/Reflect & Generators 8

new Proxy(target, handler)
Create a proxy to intercept operations
handler.get(target, key, receiver)
Intercept property reads
handler.set(target, key, value)
Intercept property sets
Reflect.get(obj, key)
Reflect API (forward default behavior)
function* gen() { yield 1; yield 2; }
Generator function
const it = gen(); it.next(); it.next()
Manually iterate a generator
for (const v of gen()) {}
Consume a generator with for...of
const wm = new WeakMap()
WeakMap (object keys only, weak refs)

Parameter matrix

ParameterEffectExample
let x = 1Block-scoped variable, reassignable within the blocklet x = 1
const PI = 3.14Block-scoped constant; the binding cannot be reassignedconst PI = 3.14
(x) => x * 2Arrow function with an implicit return for single-expression bodies and no this bindingconst fn = (x) => x * 2
(x, y = 1) => ...Default parameter, effective only when omitted or passed as undefinedconst fn = (x, y = 1) => x + y
(...args) => argsRest parameter, collecting extra arguments into a real arrayconst fn = (...args) => args
fn(...[1, 2, 3])Spread operator, expanding iterables; also used for shallow copiesfn(...[1, 2, 3])
user?.nameOptional chaining, returning undefined as a whole when any link in the chain is null/undefineduser?.name
value ?? "default"Nullish coalescing, taking the default only for null/undefinedvalue ?? "default"
`Hello ${name}`Template string interpolation, supporting multiple lines`Hello ${name}`
const [a, b] = [1, 2]Array destructuring, unpacking by positionconst [a, b] = [1, 2]
const { name, age } = userObject destructuring, extracting fields by property nameconst { name, age } = user
const { name: userName } = userDestructuring rename, binding a property to a new variable nameconst { name: userName } = user
const [a, ...rest] = [1, 2, 3]Destructuring plus rest: everything not named explicitly is collected into an array.const [a, ...rest] = [1, 2, 3]
function fn({ name, age = 18 }) {}Parameter destructuring — the formal parameters come straight off the object, defaults included.function fn({ name, age = 18 }) {}
new Promise((resolve, reject) => {})Create a promise; call resolve on success and reject on failure.new Promise((resolve, reject) => {})
.then().catch().finally()Chain handlers for fulfilment, for failure, and for cleanup that always runs.promise.then().catch().finally()
Promise.all([p1, p2])Wait for all of them in parallel; a single rejection rejects the whole set.Promise.all([p1, p2])
Promise.allSettled([p1, p2])Wait for every one to settle, then report each outcome separately.Promise.allSettled([p1, p2])
Promise.race([p1, p2])Take whichever settles first — the usual building block for a timeout guard.Promise.race([p1, p2])
Promise.any([p1, p2])Take the first one to fulfil; it rejects only when every one of them fails.Promise.any([p1, p2])
async function fn() { await promise }async/await sugar: await suspends the function until the promise settles.async function fn() { await promise }
try { await fn() } catch (e) {}try/catch around await turns a rejection into something you can handle.try { await fn() } catch (e) {}
export const fnNamed export; import it later under the same identifier.export const fn = () => {}
export default fnDefault export, at most one per module, and the importer may rename it.export default fn
import { fn } from "./module"Named import of a single binding.import { fn } from "./module"
import * as mod from "./module"Pull the whole module in as a namespace object.import * as mod from "./module"
import("./module").then(mod => {})Dynamic import, loaded on demand at runtime, which is what makes code splitting possible.import("./module").then(mod => {})
class User { constructor() {} }Class definition syntax.class User { constructor() {} }
class Admin extends User {}Inherit from a base class.class Admin extends User {}
new Map()A mapping whose keys may be objects, not just primitives.const map = new Map()
new Set([1, 2, 3])A collection that drops duplicates as they are added.const set = new Set([1, 2, 3])
for (const [k, v] of map) {}Walk Map entries through the iterator protocol as key/value pairs.for (const [k, v] of map) {}
Array.from(set)Turn any iterable into a real array.Array.from(set)
Symbol("id")Mint a unique identifier so a key cannot collide with an existing name.Symbol("id")
new WeakMap()A weak mapping whose keys must be objects, and which never keeps them alive for the collector.const wm = new WeakMap()
new Proxy(target, handler)Create a proxy that intercepts property operations on the target.new Proxy(target, handler)
handler.get(target, key, receiver)Trap property reads.handler.get(target, key, receiver)
handler.set(target, key, value)Trap property writes.handler.set(target, key, value)
Reflect.get(obj, key)Forward to the default behaviour instead of hand-rolling the same logic.Reflect.get(obj, key)
function* gen() { yield 1 }A generator: execution suspends at each yield and can hand a value back out to the caller.function* gen() { yield 1; yield 2; }
it.next()Advance the generator by hand.const it = gen(); it.next(); it.next()
for (const v of gen()) {}Drain a generator with for...of.for (const v of gen()) {}

Common pitfalls

SymptomYou wrote await in an async function, but the code after it still ran immediately.

CauseThe caller did not await or receive the returned Promise with .then, so after the function body suspends at the await no one continues, and the async chain breaks at that layer.

FixThread await through the whole call chain; or wrap it with try { await fn() } catch (e) {} to explicitly wait for the Promise to settle.

SymptomYou aggregate several requests with Promise.all, and one interface failing occasionally discards the whole batch.

CausePromise.all is all-or-nothing: any reject makes the whole thing reject immediately.

FixSwitch to Promise.allSettled([p1, p2]), iterate the results after all settle, and branch on the status of each one.

SymptomEvent callbacks registered in a loop all print the last value of index.

CauseDeclaring the loop variable with var gives no block scope, so all closures share the same variable.

FixChange the loop variable to let; block scope gives each iteration its own binding.

SymptomA user-entered 0 or empty string is treated as "not filled in," taking the default branch.

CauseYou used || for the fallback, but 0, "", and false are all falsy and get replaced too.

FixUse nullish coalescing value ?? "default", which takes the default only for null and undefined.

SymptomAfter passing an object method to a callback, this becomes undefined, throwing a property-read error.

CauseArrow functions do not bind their own this and inherit the enclosing one at definition; an ordinary function called standalone loses this.

FixWhen you need a dynamic this, use an ordinary function and pass the context explicitly, or use an arrow function but ensure the enclosing scope is the this you want.

SymptomYou push to an array declared with const and are confused that it is judged as "a constant being modified."

Causeconst locks the binding between variable and property; the array contents are still a mutable reference.

FixWhen you truly need immutable semantics, reassign the whole variable (const arr = [...arr, item]) instead of calling mutating methods.

SymptomDestructuring throws "Cannot destructure property of undefined."

CauseThe data arrives asynchronously and is still undefined when destructuring runs; or the code destructures a possibly-null nested value directly.

FixFall back for null values before destructuring, or give the function parameter a default: function fn({ name, age = 18 } = {}) {}.

SymptomA WeakMap used as an ordinary cache throws the moment a primitive is written as a key.

CauseWeakMap keys must be objects; a primitive is rejected outright.

FixSwitch to a Map when the key may be any value, and reach for a WeakMap only when you actually want its keys to stay collectable.

Troubleshooting

  1. 1Confirm whether a value is null or undefined

    console.log(user?.name)

    Optional chaining short-circuits to undefined when any link is null/undefined, which you can use to pinpoint where the chain breaks.

  2. 2Observe the final state of a Promise

    promise.then().catch().finally()

    The three stages print the success value, the error, and the cleanup info respectively, so you can tell whether the resolve value is wrong or a reject was swallowed.

  3. 3Verify the settled results of multiple async tasks

    Promise.allSettled([p1, p2])

    Each item in the returned array carries a status field, showing clearly which succeeded, which failed, and why.

  4. 4Implement a timeout fallback with race

    Promise.race([p1, p2])

    Put a Promise that rejects after a delay into race, and the timeout path settles first, ending the wait.

  5. 5Check what a module actually exports

    import * as mod from "./module"

    Printing the namespace object shows all exports, letting you confirm whether a named import spelling matches.

  6. 6Step a generator by hand to watch it suspend and resume

    const it = gen(); it.next(); it.next()

    Every next() call returns { value, done }, which makes the pause at a yield and the resumption after it plainly visible.

Command Examples

Destructuring with defaults, rest parameters, and spread

function log(label, { name, age = 18 } = {}, ...tags) { console.log(label, name, age, tags) } const [a, b] = [1, 2] const { name: userName } = { name: "tom" } log("hi", { name: "amy" }, "x", "y")

Giving the object parameter a = {} default means callers may omit it entirely without a TypeError at the destructuring site. The age default applies only when the argument is undefined (or absent); passing null keeps null.

Optional chaining plus nullish coalescing for nested config

const city = user?.address?.city ?? "unknown" const items = cart?.items?.length ?? 0 const text = `Hi ${name}, you have ${items} items`

Writing the default with || also swallows 0 and the empty string: cart.items.length || "unknown" reports "unknown" for an empty cart. ?? reacts to null/undefined only, which is what a default is supposed to mean.

Concurrent requests: allSettled to keep partial results

const results = await Promise.allSettled([fetchUser(id), fetchOrders(id), fetchStats(id)]) const ok = results.filter(r => r.status === "fulfilled").map(r => r.value) const failed = results.filter(r => r.status === "rejected")

Promise.all rejects as soon as one input fails, discarding the others while they keep running. allSettled waits for every outcome and reports each status, which is what enables partial degradation.

async/await with try/catch to run steps in order

async function bootstrap() { try { const user = await loadUser() const orders = await loadOrders(user.id) render({ user, orders }) } catch (e) { console.error("failed:", e.message) } finally { hideSpinner() } }

Sequential awaits run in order and each one can be wrapped in its own try/catch. Switching to Promise.all gives up that stepwise degradation in favor of an all-or-nothing fallback, while finally guarantees the spinner always clears.

Walking a Map/Set and consuming a generator

const map = new Map([["a", 1], ["b", 2]]) for (const [k, v] of map) { console.log(k, v) } const set = new Set([1, 2, 2, 3]) const arr = Array.from(set) function* ids() { yield 1 yield 2 } for (const v of ids()) { console.log(v) }

A Set deduplicates by construction and Array.from turns it into an ordinary array. A generator produces no intermediate array and holds constant memory even for unbounded sequences, with for...of exiting when the iterator is exhausted.

Common Pitfalls

  • ?? cannot be mixed directly with || or &&: a ?? b || c is a SyntaxError and needs (a ?? b) || c. Otherwise the whole module fails at parse time and the console often only points at the import statement.
  • Destructuring null or undefined throws a TypeError: const { name } = user blows up when user is empty, which is common when an API omits a field. Give the destructured object itself a = {} default.
  • Promise.all rejects on the first rejection without cancelling the others, which keep consuming connections and bandwidth. Use allSettled or per-promise .catch when partial failure must still render.
  • Arrow functions have no own this. Inside object methods and classes it resolves to the enclosing scope (often window or undefined), the same failure that makes this disappear in event callbacks. Use function or an explicit bind.
  • Node and browsers resolve ESM imports differently: Node requires the full file extension while browsers and most bundlers default to omitting it, so the same path yields ERR_MODULE_NOT_FOUND or a 404 after switching environments.
  • Object spread {...obj} is ES2018 syntax. Handing the file to a target that only supports ES6 — Babel transpiles syntax but does not polyfill built-ins — leaves Object.entries or Array.flat silently missing with no error.

Tips

  • const objects/arrays are still mutable inside; only the reference is immutable.
  • Arrow functions have no this of their own; they inherit the enclosing scope.
  • async/await is syntactic sugar over Promises; catch errors with try/catch.
  • ?. and ?? are ES2020: ?. short-circuits to undefined, ?? triggers only on null/undefined.
  • Proxy is commonly used for reactivity (e.g. Vue 3 reactive); Reflect forwards the matching default behavior.
  • A generator pauses at yield and returns a value; calling next() resumes execution.
  • WeakMap/WeakSet keys are weak references and don't block GC - good for caches.

FAQ

Can I still mutate an object declared with const, if const means constant?

const freezes the binding, not the value. After const obj = { a: 1 }, writing obj.a = 2 is perfectly legal because object properties stay writable; what is forbidden is re-assigning obj = {...}, which throws a TypeError. Arrays behave the same: const list = [1]; list.push(2) is fine, list = [] throws. The practical convention is to default to const and reach for let only when a variable really is reassigned — and note that Map.set, Set.add and .delete count as mutating content, which const allows.

Where exactly do ?? and || differ when falling back to a default value?

The difference is which falsy values get replaced. || triggers on every falsy value — 0, "", false and NaN included — while ?? only falls back for null and undefined. So count ?? 0 correctly preserves a legitimate 0, whereas count || 0 would overwrite it, a real bug when zero is a meaningful count or an empty string is valid input. Pair it with ?. : user?.name short-circuits the whole chain to undefined when user is null or undefined, replacing nested user && user.name guards.

How do I choose between Promise.all and Promise.allSettled for a batch of requests?

The deciding question is whether one failure should sink the whole batch. Promise.all fires everything in parallel but rejects as soon as any single promise rejects, and you lose the results of the others — right for all-or-nothing consistency. Promise.allSettled waits for every promise to settle and resolves to an array of per-item outcome objects carrying status and, on failure, reason; one rejection never blocks the rest, which is what health checks and multi-endpoint dashboards need. If you want the first success, use Promise.any, which rejects only when all of them fail.

Why can an arrow function not be used directly as an object method?

Because an arrow function has no own this — it inherits whatever this was in the enclosing scope. Writing obj.handle = () => this captures the module or outer this, not obj, and arrow functions have no arguments either, so they break code that relies on dynamic this. Use a normal function handle() {} when you need dynamic this (calling it as obj.handle() binds this to obj), or the class shorthand method handle() {}. Reserve arrows for callbacks where you deliberately want to capture the outer this, such as class fields written as handle = () => {}.

How does dynamic import() differ from a static import in the bundle output?

A static import { fn } from "./module" is a build-time dependency: the bundler resolves it during the build and inlines it into the same chunk whether or not it runs. import("./module").then(mod => {}) returns a Promise and resolves the module at runtime, so it can be split into its own chunk and only downloaded when actually triggered — the basic mechanism behind code splitting. The cost is that the call site must handle asynchrony: exports are read off the mod parameter inside then (mod.fn()) rather than used directly. import * as mod from "./module" binds the whole module as one namespace object, which suits bulk re-export scenarios.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us