A cheat sheet of the JavaScript I actually write, from ES2015 to ES2025. One short example per feature, no theory. Everything here runs in current browsers and Node 24.
Declaring variables
let
The let keyword declares block-scoped variables in contrast to the var keyword which declares function-scoped or global variables.
const
The const keyword declares block-scoped constants.
Destructuring assignment
let { bar } = foo // where foo = { bar:10, baz:12 };
const { baz } = foo // where foo = { bar:10, baz:12 };
// with a default value
const { qux = 0 } = foo // qux = 0
// arrays, with the rest
const [first, ...rest] = [1, 2, 3] // first = 1, rest = [2, 3]Strings
Template literals
const name = 'Achilles'
const greeting = `Hello ${name}` // "Hello Achilles"Arrow functions
// No semicolons here, so a line that starts with ( or [ would continue
// the line before it. Start such a line with ; when it follows a statement.
// 1. Typical arrow function.
(a, b) => {
return a + b
}
// 2. One-liner without braces, the return is implied.
(a, b) => a + b
// 3. With one argument the parentheses can be removed.
a => a + 1
// 4. Return an object, wrap it in parentheses.
const toUser = (name) => ({ name })Arrow functions and this
Arrow functions have no this of their own. They use the this of the code around them, which is what you want in callbacks.
const timer = {
seconds: 0,
start() {
setInterval(() => {
this.seconds++ // arrow functions use the `this` of the surrounding code, here the timer
}, 1000)
},
}Objects
Spread
const defaults = { theme: 'light', lang: 'en' }
const settings = { ...defaults, theme: 'dark' } // { theme: 'dark', lang: 'en' }Optional chaining
const user = { name: 'Achilles', address: null }
user.address?.city // undefined instead of a TypeError
user.getName?.() // calls getName only if it existsNullish coalescing
const count = 0
count || 10 // 10, because 0 is falsy
count ?? 10 // 0, only null and undefined fall back
let limit
limit ??= 25 // assigns only if limit is null or undefinedDeep copy with structuredClone
const original = { tags: ['php'], created: new Date() }
const copy = structuredClone(original) // a deep copy, Dates and Maps included
copy.tags.push('js') // original.tags is still ['php']Object.entries() and Object.fromEntries()
const prices = { tea: 2, coffee: 3 }
const doubled = Object.fromEntries(
Object.entries(prices).map(([item, price]) => [item, price * 2]),
) // { tea: 4, coffee: 6 }Arrays
array.map()
Returns a new array populated with the results of the provided function for each iteration:
const newItems = items.map((x) => x + 1)array.filter()
Returns a new array populated with filtered results which return true in the provided function.
const filteredItems = items.filter((item) => item.length > 3)array.slice()
Returns a copy of a portion of an array.
const newItems = items.slice(2, 4)Copy an array
// Use the spread operator. This is a shallow copy, nested objects are shared (see structuredClone).
const newArray = [...oldArray]Array.from()
Creates an array from array-like objects.
Array.from([1, 2, 3], (x) => x + x) // [2, 4, 6]
// array-like objects and iterables
Array.from('abc') // ['a', 'b', 'c']
Array.from({ length: 3 }, (_, i) => i) // [0, 1, 2]array.find()
Returns the value of the first element that matches the conditions in the provided callback function.
const foundItem = items.find((item) => item.id === 3)array.findLast()
The same as find(), starting from the end.
const users = [{ name: 'A', admin: true }, { name: 'B', admin: true }, { name: 'C' }]
const lastAdmin = users.findLast((user) => user.admin) // { name: 'B', admin: true }array.at() and array.includes()
const letters = ['a', 'b', 'c']
letters.at(-1) // 'c', negative indexes count from the end
letters.includes('b') // truearray.flat()
const flat = [[1, 2], [3]].flat() // [1, 2, 3]toSorted(), toReversed() and with()
Like sort(), reverse() and array[i] = x, but they return a new array and leave the original alone.
const numbers = [3, 1, 2]
const sorted = numbers.toSorted((a, b) => a - b) // [1, 2, 3]
const reversed = numbers.toReversed() // [2, 1, 3]
const replaced = numbers.with(0, 9) // [9, 1, 2]
// numbers is still [3, 1, 2]Without the compare function, toSorted() and sort() compare values as strings, so [10, 9] stays [10, 9].
Object.groupBy()
const posts = [
{ title: 'Git reflog', tag: 'git' },
{ title: 'Linux 101', tag: 'linux' },
{ title: 'My Git cheat sheet', tag: 'git' },
]
const byTag = Object.groupBy(posts, (post) => post.tag)
// { git: [two posts], linux: [one post] }Sets
const backend = new Set(['php', 'sql'])
const frontend = new Set(['js', 'sql'])
backend.union(frontend) // Set { 'php', 'sql', 'js' }
backend.intersection(frontend) // Set { 'sql' }
backend.difference(frontend) // Set { 'php' }
const unique = [...new Set([1, 1, 2])] // [1, 2]Loops
for…of
Loops through iterable objects including arrays.
const array = [1, 2, 3]
for (const item of array) {
console.log(item)
}
// with the index
for (const [index, item] of array.entries()) {
console.log(index, item)
}forEach
You can’t stop a forEach. return only skips to the next item, and break is a SyntaxError. Use for...of or some() when you need to stop early.
array.forEach(function (item) {
console.log(item)
})for…in
Loop through object properties. Avoid using for...in with arrays.
const object = { a: 1, b: 2, c: 3 }
for (const property in object) {
console.log(property)
}Classes
class Car extends Vehicle {
constructor(name) {
super() // Call the parent constructor.
this.name = name
}
changeName(name) {
this.name = name
}
}
const car1 = new Car("BMW")Private and static fields
class Counter {
#count = 0 // private field
static instances = 0 // static field
constructor() {
Counter.instances++
}
increment() {
return ++this.#count
}
get value() {
return this.#count
}
}
const counter = new Counter()
counter.increment() // 1
counter.value // 1
// counter.#count outside the class is a SyntaxErrorModules
Native modules in the browser and in Node need the file extension in the import path.
Named exports
// Named export.
export class Car extends Vehicle {}
// Named import.
import { Car } from "./car.js"Default exports
// Default export.
export default class Car extends Vehicle {}
// Default import.
import Car from "./car.js"Dynamic import
// Load a module only when it is needed.
const { Car } = await import("./car.js")Top-level await
// In a module, await works outside async functions.
const config = await fetch('/config.json').then((response) => response.json())Promises
Basics
let promise = new Promise(function(resolve, reject) {
// async logic ...
if (everythingIsOk) {
resolve("Success!");
}
else {
reject("Something went wrong!");
}
});
promise.then((message) => {
// do something.
}).catch((message) => {
// do something with the error.
})Promise.all
Waits for several promises at once.
Promise.all([promise1, promise2, promise3]).then((messages) => {
// messages is an array with the results, in the same order.
// It resolves when all the promises have resolved,
// and rejects as soon as one of them rejects.
})Promise.allSettled
Waits for all of them, whether they resolve or reject.
const results = await Promise.allSettled([promise1, promise2])
// [{ status: 'fulfilled', value: ... }, { status: 'rejected', reason: ... }]Promise.any
// Resolves with the first promise that fulfills, rejects only if all of them reject.
const fastest = await Promise.any([fetchFromMirror1(), fetchFromMirror2()])Async/await
async function doStuff() {
try {
// Waits for the resolved promise value.
let result = await asyncFunction()
} catch (error) {
// Get the rejected promise value.
}
}fetch with a timeout
const response = await fetch(url, {
signal: AbortSignal.timeout(5000), // give up after 5 seconds
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const posts = await response.json()