JavaScript is the language of the web: every browser runs it, and Node.js runs it
on servers and in the terminal. The reference below is grouped by what you are
trying to do, and the filter box searches all of it at once. Type array and
everything about arrays comes to you, or type ES2025 to see what the latest
edition added.
Every snippet is checked against ECMAScript 2025, the current edition of the
standard, and was run on Node.js 24. Anything added after ES2020 says which edition
in the notes column. Names like user, nums and el are placeholders for your
own. Coming from another language? The Python,
Ruby and PHP cheat
sheets are grouped the same way, so they read side by side. Adding types? The
TypeScript cheat sheet picks up where this
one stops. The regex rows in the strings table are the basics: the
Regex cheat sheet has the pattern syntax itself,
from character classes to lookbehind.
Searches the task, the command and the third column. Press / from anywhere on the page.
287 commands
Running JavaScript
| Task | Code | Notes |
|---|---|---|
| Check which Node.js version you have | node --version | |
| Run a file with Node.js | node app.js | |
| Open an interactive shell | node | Leave with .exit or Ctrl+D. The browser console (F12) works the same way |
| Run one line without a file | node -e 'console.log(2 ** 53)' | |
| Print the result of an expression | node -p '[1, 2, 3].length' | |
| Rerun every time you save | node --watch app.js | Stable in Node 22 |
| Load variables from a .env file | node --env-file=.env app.js | Node 20.6+. Read them with process.env.API_KEY |
| Load a script in a web page | <script src="app.js" defer></script> | defer runs it after the HTML is parsed, in order |
| Load a module in a web page | <script type="module" src="main.js"></script> | Deferred automatically, and can use import |
| Print to the console | console.log("total", total) | Several values are separated by a space |
| Print an array of objects as a table | console.table(rows) | |
| Warnings and errors | console.warn("low disk") console.error(err) | In Node these go to stderr, not stdout |
| Time a piece of code | console.time("load") console.timeEnd("load") | Prints load: 12.3ms, with the real time |
| Pause in the debugger | debugger; | Only stops while the browser DevTools are open, or under node --inspect |
| Strict mode | "use strict"; | First line of a script. Modules and classes are strict already |
Variables and types
| Task | Code | Notes |
|---|---|---|
| A variable you will reassign | let count = 0; | |
| A variable you will not reassign | const name = "Ada"; | The default choice. A const array can still be pushed to |
| The old way you will still see | var total = 0; | Scoped to the function, not the block. Use let or const |
| Comment | // one line /* several lines */ | |
| The seven primitive types | string number bigint boolean undefined null symbol | Everything else, arrays and functions included, is an object |
| What type is it | typeof value | Returns a string. typeof null is "object", a bug kept for compatibility |
| Is it an array | Array.isArray(value) | typeof [] is "object" |
| Is it made by a class | value instanceof Date | |
| No value | undefined null | undefined is what you get for something missing. null is what you set on purpose |
| Numbers | 42 3.14 1_000_000 0xff 1e3 | One number type, a 64-bit float. Underscores are ignored (ES2021) |
| Largest safe whole number | Number.MAX_SAFE_INTEGER | 2 ** 53 - 1. Past it, whole numbers lose precision |
| Bigger whole numbers | 123n BigInt("9007199254740993") | A BigInt. Cannot be mixed with numbers: 1n + 1 is a TypeError |
| String to number | Number("42") +"42" | NaN when it is not a number. Number("") is 0 |
| Read a number from the start of a string | parseInt("42px", 10) parseFloat("3.5kg") | Stops at the first character that does not fit. Always pass the base |
| Is it NaN | Number.isNaN(x) | NaN === NaN is false, so x === NaN never works |
| Is it a whole number | Number.isInteger(x) | |
| Number to string | String(42) n.toString(2) | toString(2) gives binary: (5).toString(2) is 101 |
| Round to 2 decimal places | n.toFixed(2) | Returns a string: (3.14159).toFixed(2) is 3.14 |
| Round, down, up, cut off | Math.round(x) Math.floor(x) Math.ceil(x) Math.trunc(x) | Halves round up: Math.round(2.5) is 3 and Math.round(-2.5) is -2 |
| Random whole number from 1 to 6 | Math.floor(Math.random() * 6) + 1 | Not for passwords or tokens. crypto.randomUUID() for an ID |
| Largest and smallest | Math.max(3, 7, 1) Math.min(...nums) | |
| Powers and roots | 2 ** 10 Math.sqrt(16) Math.abs(-4) | |
| Infinity | 1 / 0 | Infinity, not an error. Number.isFinite(x) rules it out |
The falsy values are false, 0, -0, 0n, the empty string, null, undefined and NaN. Everything else is truthy, including the string "0", an empty array and an empty object, so if (items.length) is how to ask whether an array has anything in it.
Operators
| Task | Code | Notes |
|---|---|---|
| Equal, and not equal | a === b a !== b | Strict: no type conversion. Use these by default |
| Loose equality | a == b a != b | Converts types first, so 0 == "" and "1" == 1 are both true |
| Is it null or undefined | x == null | The one common use of ==. Matches null and undefined only |
| Same value, NaN included | Object.is(a, b) | Object.is(NaN, NaN) is true, Object.is(0, -0) is false |
| Arithmetic | a + b a - b a * b a / b | 7 / 2 is 3.5. There is no separate integer division |
| Whole-number division | Math.trunc(7 / 2) | 3. Math.floor(-7 / 2) is -4, Math.trunc(-7 / 2) is -3 |
| Remainder | 7 % 3 | Keeps the sign of the left side: -7 % 3 is -1 |
| Raise to a power | 2 ** 10 | 1024. Not ^, which is bitwise xor |
| Add or subtract one | i++ i-- i += 5 | |
| And, or, not | a && b a || b !a | && and || return one of the two values, not always true or false |
| Default when null or undefined | const port = config.port ?? 3000; | ES2020. Keeps 0 and the empty string, unlike || |
| Default when falsy | const name = input || "Anonymous"; | Also replaces 0 and the empty string |
| Set only if missing | options.retries ??= 3; | ES2021. ||= and &&= work the same way |
| Read a property that might not be there | user?.address?.city | ES2020. undefined instead of a TypeError |
| Call a method that might not be there | options.onDone?.() | |
| Pick one of two values | const label = n === 1 ? "item" : "items"; | |
| Force a true or false | Boolean(value) !!value | |
| Spread into a new array or object | [...a, ...b] { ...defaults, ...overrides } | In an object, later keys win |
| Does the object have this key | Object.hasOwn(user, "name") | ES2022. "name" in user also looks up the prototype chain |
| Remove a property | delete user.age | |
| Compare strings | "apple" < "banana" | Compares character codes, so "Z" < "a" is true. Sort words with localeCompare |
| String and number mixed | "5" + 3 "5" - 3 | "53" and 2. + joins if either side is a string, - always converts to numbers |
Strings and template literals
Strings cannot be changed in place. Every method returns a new string, so assign the result: s = s.trim(). Positions start at 0.
| Task | Code | Notes |
|---|---|---|
| Single, double or backtick quotes | 'cat' "cat" `cat` | The same string. Backticks also allow placeholders and line breaks |
| Put a value in a string | `Hello, ${name}` | A template literal. Any expression goes inside the placeholder |
| Expression in a template literal | `${items.length} ${items.length === 1 ? "item" : "items"}` | |
| Length | s.length | Counts UTF-16 units, so an emoji is usually 2. [...s].length counts code points |
| One character | s[0] s.at(-1) | First and last. at is ES2022 and counts back from the end with a negative |
| Part of a string | s.slice(1, 4) s.slice(-3) | The end position is not included |
| Change case | s.toUpperCase() s.toLowerCase() | |
| Trim whitespace | s.trim() s.trimStart() s.trimEnd() | |
| Split into an array | s.split(",") | [...s] splits by code point, so most emoji stay whole |
| Join an array into a string | words.join(", ") | |
| Replace the first match | s.replace("cat", "dog") | Only the first, when the pattern is a string |
| Replace every match | s.replaceAll("cat", "dog") | ES2021 |
| Does it contain | s.includes("cat") | Case-sensitive |
| Starts or ends with | s.startsWith("http") s.endsWith(".js") | |
| Find the position | s.indexOf("cat") | -1 when missing |
| Pad to a length | String(7).padStart(3, "0") | 007. padEnd pads on the right |
| Repeat | "-".repeat(20) | |
| Sort order of two strings | a.localeCompare(b) | Negative, zero or positive. Handles accents and case sensibly |
| Test against a regex | /^\d+$/.test(s) | True when s is only digits |
| Every match of a regex | s.match(/\d+/g) | An array of strings, or null when nothing matches |
| Every match, with capture groups | [...s.matchAll(/(\w+)=(\d+)/g)] | Each item is a match: m[1] and m[2] are the groups |
| Replace by regex | s.replace(/\s+/g, " ") | Without the g flag, only the first match |
| Make user input safe inside a regex | new RegExp(RegExp.escape(input)) | ES2025. Node 24 and current browsers |
| Number with thousands separators | n.toLocaleString("en-GB") | 1234567.5 becomes 1,234,567.5 |
| Money | new Intl.NumberFormat("en-GB", { style: "currency", currency: "GBP" }).format(9.5) | £9.50 |
| Character codes | "A".codePointAt(0) String.fromCodePoint(65) | 65 and A. Works for emoji, unlike charCodeAt |
| Newline and tab | "line\n" "col\t" | |
| Backslashes kept as typed | String.raw`C:\new\folder` | |
| Base64 encode and decode | btoa("hi") atob("aGk=") | Latin-1 text only. Anything else throws |
| Encode for a URL | encodeURIComponent(query) | For one value in a query string. decodeURIComponent reverses it |
Arrays and array methods
An array is an ordered list that can hold any mix of values. Some methods change the array in place and some return a new one. The notes say which.
| Task | Code | Notes |
|---|---|---|
| Create an array | const nums = [10, 20, 30]; | Empty: [] |
| Array of the numbers 0 to 4 | Array.from({ length: 5 }, (_, i) => i) | |
| Array of five zeros | new Array(5).fill(0) | Only for plain values. fill({}) puts one shared object in every slot |
| Read by position | nums[0] nums.at(-1) | undefined past the end, not an error |
| How many items | nums.length | |
| Is it empty | nums.length === 0 | An empty array is truthy, so if (nums) is always true |
| Add to the end | nums.push(40) | In place. Returns the new length |
| Remove from the end | nums.pop() | In place. Returns the removed item |
| Add or remove at the start | nums.unshift(5) nums.shift() | In place |
| Remove or insert in the middle | nums.splice(1, 2) nums.splice(1, 0, 15) | In place. Remove 2 items at position 1, or insert 15 there |
| Copy part of an array | nums.slice(1, 3) | New array. The end position is not included |
| Join two arrays | [...a, ...b] a.concat(b) | |
| Copy | [...nums] | Shallow: objects inside are still shared |
| Deep copy | structuredClone(data) | Copies nested arrays, objects, Dates, Maps and Sets. Not functions |
| Transform every item | nums.map((n) => n * 2) | New array, same length |
| Keep some items | nums.filter((n) => n > 15) | New array |
| Combine into one value | nums.reduce((sum, n) => sum + n, 0) | Always pass the starting value: an empty array without one throws |
| Run code for each item | nums.forEach((n, i) => console.log(i, n)) | Cannot break out. Use for...of when you need to |
| First item that matches | nums.find((n) => n > 15) | undefined when nothing matches |
| Position of the first match | nums.findIndex((n) => n > 15) | -1 when nothing matches |
| Last item that matches | nums.findLast((n) => n > 15) | ES2023. findLastIndex for the position |
| Is the value in the array | nums.includes(20) | Finds NaN too, unlike indexOf |
| Position of a value | nums.indexOf(20) | -1 when missing |
| Do any or all match | nums.some((n) => n > 25) nums.every((n) => n > 0) | |
| Sort numbers | nums.sort((a, b) => a - b) | In place. With no function it sorts as strings: [10, 9, 1] becomes [1, 10, 9] |
| Sort largest first | nums.sort((a, b) => b - a) | |
| Sort words | names.sort((a, b) => a.localeCompare(b)) | |
| Sort objects by a field | people.sort((a, b) => a.age - b.age) | |
| Sorted copy | nums.toSorted((a, b) => a - b) | ES2023. Leaves nums alone. Also toReversed and toSpliced |
| Copy with one item changed | nums.with(0, 99) | ES2023 |
| Reverse | nums.reverse() | In place |
| Flatten nested arrays | nested.flat() nested.flat(Infinity) | One level by default |
| Map, then flatten one level | lines.flatMap((line) => line.split(" ")) | |
| Group by a field | Object.groupBy(people, (p) => p.team) | ES2024. An object of arrays. Map.groupBy returns a Map |
| Remove duplicates | [...new Set(items)] | Keeps the first of each, in order |
| Unpack into variables | const [first, second, ...rest] = nums; | |
| Swap two variables | [a, b] = [b, a]; | |
| Array from anything list-like | Array.from(new Set([1, 2])) Array.from("abc") | [1, 2] and [a, b, c] |
| Empty the array | nums.length = 0; | In place, so every reference sees it |
Objects and destructuring
| Task | Code | Notes |
|---|---|---|
| Create an object | const user = { name: "Ada", age: 36 }; | Empty: {} |
| Read a property | user.name user["name"] | undefined when missing. Brackets for a key held in a variable |
| Set a property | user.email = "ada@example.com"; | |
| Same name for key and variable | const user = { name, age }; | Short for { name: name, age: age } |
| Key from a variable | const totals = { [key]: 0 }; | |
| Method | const cat = { speak() { return "meow"; } }; | |
| Keys, values, or both | Object.keys(user) Object.values(user) Object.entries(user) | Arrays. entries gives [key, value] pairs |
| Loop over keys and values | for (const [key, value] of Object.entries(user)) {} | |
| Object from pairs | Object.fromEntries(pairs) | Reverses Object.entries, so you can map or filter an object through an array |
| Merge into a new object | const settings = { ...defaults, ...options }; | Shallow. Later keys win |
| Merge into an existing object | Object.assign(target, source) | |
| Copy without some keys | const { password, ...safeUser } = user; | |
| Pull out properties | const { name, age } = user; | |
| Pull out and rename | const { name: userName } = user; | |
| Default for a missing property | const { role = "member" } = user; | Used for undefined only, not null |
| Pull out a nested property | const { address: { city } } = user; | TypeError if address is missing |
| Destructure in the parameter list | function greet({ name, greeting = "Hi" }) {} | |
| Stop changes | Object.freeze(config) | Shallow. Changes are ignored, or a TypeError in strict mode |
| To a JSON string | JSON.stringify(user) JSON.stringify(user, null, 2) | The second indents by 2. Drops undefined and functions. Dates become strings |
| From a JSON string | JSON.parse(text) | SyntaxError when the JSON is not valid |
| Map: any value as a key | const m = new Map(); m.set(key, value); m.get(key) | Keys can be objects. Keeps insertion order. m.size, m.has, m.delete |
| Count how often each value appears | counts.set(w, (counts.get(w) ?? 0) + 1); | counts is a Map |
| Map from an object | new Map(Object.entries(obj)) | And back: Object.fromEntries(map) |
| Set: unique values | const tags = new Set(["js", "css", "js"]); | tags.size is 2. tags.add, tags.has, tags.delete |
| Combine two sets | a.union(b) a.intersection(b) a.difference(b) | ES2025. Also symmetricDifference and isSubsetOf |
Two objects are only === when they are the same object. { a: 1 } === { a: 1 } is false, and there is no built-in deep comparison.
Control flow and loops
| Task | Code | Notes |
|---|---|---|
| If, else if, else | if (x > 0) {} else if (x < 0) {} else {} | |
| Switch | switch (cmd) { case "start": run(); break; default: help(); } | Compares with ===. Without break, it falls through to the next case |
| Loop a number of times | for (let i = 0; i < 5; i++) {} | 0 to 4 |
| Loop over an array's values | for (const item of items) {} | Also works on strings, Maps, Sets and NodeLists |
| Loop with the position | for (const [i, item] of items.entries()) {} | |
| Loop over a Map | for (const [key, value] of map) {} | |
| Loop over an object's keys | for (const key in obj) {} | Includes inherited keys. Do not use it on arrays |
| While loop | while (queue.length > 0) {} | |
| Run at least once | do {} while (retry); | |
| Leave a loop | break; | |
| Skip to the next item | continue; | |
| Leave two loops at once | outer: for (const row of grid) { for (const cell of row) { if (cell) break outer; } } | A labelled break |
Functions and arrow functions
| Task | Code | Notes |
|---|---|---|
| Function declaration | function add(a, b) { return a + b; } | Can be called above the line that defines it |
| Arrow function | const add = (a, b) => a + b; | One expression is returned without writing return |
| Arrow function with a body | const add = (a, b) => { return a + b; }; | With braces, you need return |
| Arrow with one parameter | const double = n => n * 2; | The brackets are optional |
| Return an object from an arrow | const makeUser = (name) => ({ name }); | Wrap it in brackets, or the braces are read as the body |
| Function expression | const add = function (a, b) { return a + b; }; | |
| Default value | function greet(name = "world") {} | Used for undefined, not null |
| Any number of arguments | function sum(...nums) { return nums.reduce((a, b) => a + b, 0); } | nums is a real array |
| Spread an array into arguments | Math.max(...nums) | |
| Named options | function connect({ host, port = 5432 } = {}) {} | The = {} lets it be called with nothing |
| Return several values | return [min, max]; | Then const [min, max] = range(nums) |
| Run code later | setTimeout(() => console.log("later"), 1000); | Milliseconds. clearTimeout(id) cancels it |
| Run code repeatedly | const id = setInterval(tick, 1000); clearInterval(id); | |
| Closure | function counter() { let n = 0; return () => ++n; } | Each call to counter gets its own n |
| Generator | function* ids() { let i = 1; while (true) yield i++; } | const gen = ids(); gen.next().value is 1, then 2 |
| Fix this for later | const handler = this.handleClick.bind(this); | |
| Call with a chosen this | fn.call(obj, a, b) fn.apply(obj, [a, b]) | |
| Run once, straight away | (() => { const secret = 42; })(); | Keeps its variables private. Rare now that modules exist |
Arrow functions have no this of their own. They use the this of the code around them, which makes them right for callbacks inside a method and wrong as the method itself.
Classes
| Task | Code | Notes |
|---|---|---|
| Define a class | class Point { constructor(x, y) { this.x = x; this.y = y; } } | |
| Create an object | const p = new Point(1, 2); | Calling a class without new is a TypeError |
| Method | dist() { return Math.hypot(this.x, this.y); } | Inside the class body. No function keyword, no commas |
| Field with a starting value | count = 0; | ES2022. In the class body, set on every new object |
| Private field | #balance = 0; | ES2022. this.#balance works only inside the class |
| Private method | #check(amount) {} | ES2022 |
| Does it have the private field | static isAccount(obj) { return #balance in obj; } | ES2022 |
| Getter | get area() { return this.w * this.h; } | Read as rect.area, no brackets |
| Setter | set name(value) { this.#name = value.trim(); } | Runs on user.name = " Ada " |
| Static method | static fromArray([x, y]) { return new Point(x, y); } | Called on the class: Point.fromArray([1, 2]) |
| Static field | static count = 0; | ES2022. Shared, read as Point.count |
| Inherit from a class | class Cat extends Animal {} | |
| Call the parent | super(name); super.speak(); | In a constructor, super() must come before any use of this |
| Custom string form | toString() { return `(${this.x}, ${this.y})`; } | Used by String(p) and template literals |
| Is it made by this class | p instanceof Point | True for subclasses too |
| Class name of an object | p.constructor.name |
A class body is always strict mode, and a class cannot be used above the line that defines it, unlike a function declaration. Methods live on the prototype and are shared by every instance.
Modules: import and export
A module is a file with its own scope that exports names for other files to import. Browsers load modules with script type="module". Node.js treats .mjs files, and .js files in a package whose package.json has "type": "module", as modules.
| Task | Code | Notes |
|---|---|---|
| Named export | export function add(a, b) { return a + b; } | Works on const, let and class too |
| Export several at the end | export { add, subtract }; | |
| Default export | export default class Cart {} | One per file |
| Import named exports | import { add, subtract } from "./math.js"; | Keep the .js: browsers and Node do not guess the extension |
| Import the default export | import Cart from "./cart.js"; | Any name you like, no braces |
| Import under another name | import { add as sum } from "./math.js"; | |
| Import everything | import * as math from "./math.js"; | Then math.add(1, 2) |
| Pass exports straight through | export { add } from "./math.js"; | For an index.js that gathers a folder |
| Run a module for its side effects | import "./setup.js"; | |
| Load a module only when needed | const { add } = await import("./math.js"); | Returns a promise. Keeps rarely used code out of the first load |
| Import a JSON file | import data from "./data.json" with { type: "json" }; | ES2025 |
| await at the top of a module | const config = await loadConfig(); | ES2022. Modules only |
| This file's URL and folder | import.meta.url import.meta.dirname | dirname and filename are Node 20.11+ |
| Import a Node.js built-in | import { readFile } from "node:fs/promises"; | |
| The older Node.js style, CommonJS | const fs = require("node:fs"); module.exports = { add }; | Still common in older packages and tutorials |
| Install a package from npm | npm install date-fns | Then import it by name, no ./ and no .js |
Promises and async/await
A promise is a value that arrives later. An async function always returns a promise, and await pauses that function until the promise settles, without freezing the page or blocking other code.
| Task | Code | Notes |
|---|---|---|
| Async function | async function loadUser(id) {} | Always returns a promise |
| Async arrow function | const loadUser = async (id) => {}; | |
| Wait for a promise | const res = await fetch(url); | Inside an async function, or at the top of a module |
| Fetch JSON | const data = await (await fetch(url)).json(); | Skips the status check. See the next row |
| Check the response status | if (!res.ok) throw new Error(`HTTP ${res.status}`); | fetch only rejects on network errors. A 404 or 500 still resolves |
| Send JSON | await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }); | |
| Handle a failure | try { await save(); } catch (err) { console.error(err); } | |
| Wait for several at once | const [user, posts] = await Promise.all([getUser(), getPosts()]); | In parallel. Rejects as soon as any one rejects |
| Wait for all, even failures | const results = await Promise.allSettled(tasks); | Each result has status fulfilled with a value, or rejected with a reason |
| First one to finish | await Promise.race([task, timeout]) | |
| First one to succeed | await Promise.any([mirrorA(), mirrorB()]) | ES2021. Rejects only if every one fails |
| Give up after a few seconds | await fetch(url, { signal: AbortSignal.timeout(5000) }); | Rejects with a TimeoutError |
| Cancel a request | const controller = new AbortController(); controller.abort(); | Pass controller.signal as the signal option to fetch |
| Wait a set time | const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); | Then await sleep(500) |
| Make a promise from a callback | new Promise((resolve, reject) => { img.onload = resolve; img.onerror = reject; }) | |
| A promise you resolve from outside | const { promise, resolve, reject } = Promise.withResolvers(); | ES2024 |
| Turn a throw into a rejection | Promise.try(() => JSON.parse(text)) | ES2025. Works for functions that may or may not be async |
| The older then style | fetch(url).then((res) => res.json()).then(show).catch(console.error); | |
| Run code however it ends | .finally(() => spinner.remove()) | Also try...finally with await |
| One at a time, in order | for (const url of urls) { await download(url); } | forEach does not wait: use for...of |
| Loop over an async stream | for await (const chunk of stream) {} |
An error thrown inside an async function becomes a rejected promise. If nothing awaits it or calls catch, it is an unhandled rejection: a warning in the browser console, and in Node.js the process exits with an error.
Error handling
| Task | Code | Notes |
|---|---|---|
| Catch an error | try { risky(); } catch (err) { console.error(err.message); } | |
| Catch without naming the error | try { return JSON.parse(text); } catch { return null; } | |
| Run code however the block ends | try { work(); } finally { cleanUp(); } | Runs after a return or an uncaught error too |
| Throw an error | throw new Error("age must be positive"); | Throw an Error, not a string, so there is a stack trace |
| Built-in error types | TypeError RangeError SyntaxError ReferenceError | |
| Handle one type only | if (err instanceof TypeError) { useDefault(); } else { throw err; } | catch has no type filter. Check inside it and rethrow the rest |
| Wrap an error with its cause | throw new Error("could not load config", { cause: err }); | ES2022. Read it back as err.cause |
| Your own error type | class NotFoundError extends Error { name = "NotFoundError"; } | |
| Message and stack trace | err.message err.stack | |
| Check an assumption | console.assert(total >= 0, "total went negative") | Only logs. It does not throw or stop anything |
| Report errors nothing caught, in a browser | window.addEventListener("error", (e) => report(e.error)); | |
| Report rejections nothing caught, in a browser | window.addEventListener("unhandledrejection", (e) => report(e.reason)); |
Only catch errors you can do something about. A try around a whole function also swallows the typo in a variable name you would have wanted to hear about.
Dates
A Date is a single moment in time, stored as milliseconds since 1 January 1970 UTC. It is shown in the computer's local time zone unless you ask for another.
| Task | Code | Notes |
|---|---|---|
| Now | new Date() Date.now() | A Date object, and a plain number of milliseconds |
| Parse an ISO date | new Date("2026-09-27T14:30:00Z") | A date with no time, like 2026-09-27, is read as midnight UTC |
| Make a date from parts | new Date(2026, 8, 27) | 27 September: months count from 0. Local time |
| Read parts | d.getFullYear() d.getMonth() d.getDate() d.getDay() | getMonth is 0 to 11. getDay is the weekday, 0 for Sunday |
| Standard string for storing and APIs | d.toISOString() | Always UTC: 2026-09-27T14:30:00.000Z |
| Readable date | d.toLocaleDateString("en-GB", { dateStyle: "long" }) | 27 September 2026 |
| Add days | d.setDate(d.getDate() + 7); | In place. Rolls over into the next month correctly |
| Days between two dates | Math.round((b - a) / 86_400_000) | Subtracting Dates gives milliseconds |
DOM basics
The DOM is the web page as objects you can read and change. These run in the browser, not in Node.js. Load scripts with defer or type="module" so the elements exist when the code runs.
| Task | Code | Notes |
|---|---|---|
| Find the first match | document.querySelector(".card") | Any CSS selector. null when nothing matches |
| Find by id | document.getElementById("menu") | No # before the id |
| Find every match | document.querySelectorAll("li") | A NodeList. It has forEach. [...list] for map and filter |
| Search inside one element | card.querySelector("h2") | |
| Nearest matching parent | button.closest(".card") | Checks the element itself first |
| Set text | el.textContent = "Saved"; | Always shown as text, so it is safe for user input |
| Set HTML | el.innerHTML = "<strong>Saved</strong>"; | Never with user input: it can run their code |
| Read and set attributes | link.getAttribute("href") button.setAttribute("aria-expanded", "true") | |
| data- attributes | el.dataset.userId | Reads data-user-id. Always a string |
| Add, remove or flip a class | el.classList.add("open") el.classList.remove("open") el.classList.toggle("open") | |
| Has a class | el.classList.contains("open") | |
| Set a style | el.style.backgroundColor = "gold"; | camelCase names. Toggling a class is usually cleaner |
| Hide and show | el.hidden = true; | |
| Read a form field | input.value checkbox.checked | value is always a string |
| Create an element | const li = document.createElement("li"); | |
| Add it to the page | list.append(li) | Also prepend, before and after. append takes text too |
| Remove an element | li.remove() | |
| Replace everything inside | list.replaceChildren(...items) | With no arguments, empties it |
| Listen for a click | button.addEventListener("click", (event) => {}); | |
| Listen only once | button.addEventListener("click", start, { once: true }); | |
| Stop the browser's default action | event.preventDefault(); | Stops a form submitting or a link navigating |
| One listener for many children | list.addEventListener("click", (e) => { const item = e.target.closest("li"); }); | Event delegation. Also covers items added later |
| Stop listening | button.removeEventListener("click", start); | Needs the same function, so an inline arrow cannot be removed |
| Read a whole form | Object.fromEntries(new FormData(form)) | Uses each field's name attribute. Values are strings |
| Run code once the HTML is parsed | document.addEventListener("DOMContentLoaded", init); | Not needed with defer or type="module" |
| Save a value in the browser | localStorage.setItem("theme", "dark") localStorage.getItem("theme") | Strings only. JSON.stringify an object first |
| Read the query string | new URLSearchParams(location.search).get("q") | null when missing |
| Scroll to an element | el.scrollIntoView({ behavior: "smooth" }) | |
| Copy text to the clipboard | await navigator.clipboard.writeText(text); | HTTPS or localhost only, and usually only in response to a click |
| Run code before the next repaint | requestAnimationFrame(draw); | For animation. Runs once per screen refresh, usually 60 times a second |
Array methods, start to finish
Most of the arrays table in one place: filter, map and reduce chained
together, grouping, sorting a copy, and destructuring the result.
const orders = [
{ id: 1, customer: "Ada", total: 42.5, status: "paid" },
{ id: 2, customer: "Grace", total: 18, status: "refunded" },
{ id: 3, customer: "Ada", total: 7.25, status: "paid" },
{ id: 4, customer: "Linus", total: 99.99, status: "paid" },
];
const paid = orders.filter((o) => o.status === "paid");
const revenue = paid.reduce((sum, o) => sum + o.total, 0);
console.log(revenue.toFixed(2)); // 149.74
const customers = [...new Set(paid.map((o) => o.customer))];
console.log(customers); // [ 'Ada', 'Linus' ]
const byCustomer = Object.groupBy(paid, (o) => o.customer);
console.log(byCustomer.Ada.length); // 2
const biggestFirst = paid.toSorted((a, b) => b.total - a.total);
const [top, ...others] = biggestFirst;
console.log(top.id, others.length); // 4 2
console.log(paid[0].id); // 1: toSorted left paid alone
const labels = paid.map(({ id, total }) => `#${id}: £${total.toFixed(2)}`);
console.log(labels.join(", ")); // #1: £42.50, #3: £7.25, #4: £99.99The parameter list ({ id, total }) destructures each order as it arrives, so the
arrow body never has to write o.. Object.groupBy needs ES2024; on older
browsers the same result is a reduce that pushes each order into
groups[o.customer].
Array.prototype.sort is required to be stable, so orders with the same total keep
their original order, which is what makes sorting by one field and then another
work. V8, the engine in Chrome and Node.js, uses Timsort, a mix of
merge sort and
insertion sort. Both are on
the site as step-through visualisations.
this, start to finish
this is set by how a function is called, not where it is written. This is the
bug almost everyone meets in their first week, and the three usual fixes.
"use strict";
const timer = {
seconds: 0,
tick() {
this.seconds += 1;
return this.seconds;
},
};
console.log(timer.tick()); // 1: called as timer.tick(), so this is timer
const tick = timer.tick;
try {
tick(); // called on its own, so this is undefined
} catch (err) {
console.log(err.constructor.name); // TypeError
}
// Fix 1: an arrow function, which calls the method on timer itself
const viaArrow = () => timer.tick();
console.log(viaArrow()); // 2
// Fix 2: bind, which makes a copy with this fixed
const bound = timer.tick.bind(timer);
console.log(bound()); // 3
// Fix 3: an arrow inside the method, which uses the method's own this
const clock = {
seconds: 0,
startCounting(times) {
[...Array(times)].forEach(() => {
this.seconds += 1; // the arrow has no this of its own, so it is clock
});
return this.seconds;
},
};
console.log(clock.startCounting(3)); // 3Swap the arrow in startCounting for function () { ... } and it throws a
TypeError, because a plain function passed as a callback is called on its own.
Passing timer.tick to setTimeout or addEventListener loses this the same
way: the caller decides what this is, and it is not timer.
A class with private fields
Most of the classes and errors tables in one place: a private field, a getter, a static method, a subclass and a custom error.
class InsufficientFundsError extends Error {
name = "InsufficientFundsError";
}
class Account {
#balance = 0;
static count = 0;
constructor(owner) {
this.owner = owner;
Account.count += 1;
}
get balance() {
return this.#balance;
}
deposit(amount) {
this.#check(amount);
this.#balance += amount;
return this;
}
withdraw(amount) {
this.#check(amount);
if (amount > this.#balance) {
throw new InsufficientFundsError(`${this.owner} has only ${this.#balance}`);
}
this.#balance -= amount;
return this;
}
#check(amount) {
if (!Number.isFinite(amount) || amount <= 0) {
throw new RangeError(`amount must be a positive number, got ${amount}`);
}
}
static isAccount(obj) {
return #balance in obj;
}
toString() {
return `${this.owner}: ${this.#balance}`;
}
}
class SavingsAccount extends Account {
constructor(owner, rate) {
super(owner);
this.rate = rate;
}
addInterest() {
return this.deposit(Math.round(this.balance * this.rate));
}
}
const savings = new SavingsAccount("Ada", 0.05).deposit(200).addInterest();
console.log(`${savings}`); // Ada: 210
console.log(savings.balance, Account.count); // 210 1
console.log(Account.isAccount(savings), Account.isAccount({})); // true false
try {
savings.withdraw(500);
} catch (err) {
if (err instanceof InsufficientFundsError) {
console.log(err.name, "-", err.message); // InsufficientFundsError - Ada has only 210
} else {
throw err;
}
}savings.#balance outside the class is a SyntaxError before the code even runs,
and savings.balance = 0 fails because the getter has no setter: silently in a
plain script, and with a TypeError in a module or strict code. Returning
this from deposit is what lets the calls chain.
async and await with fetch, start to finish
A small helper that does the three things every fetch call needs: check the
status, time out, and parse the JSON. Then two ways of running several requests.
async function getJson(url, { timeoutMs = 5000 } = {}) {
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
if (!res.ok) {
throw new Error(`HTTP ${res.status} from ${url}`);
}
return res.json();
}
const api = "https://api.github.com/repos";
// One request
try {
const repo = await getJson(`${api}/nodejs/node`);
console.log(repo.full_name); // nodejs/node
} catch (err) {
console.error("Could not load the repo:", err.message);
}
// Several at once: all must succeed
const names = ["nodejs/node", "denoland/deno"];
const repos = await Promise.all(names.map((name) => getJson(`${api}/${name}`)));
console.log(repos.map((r) => r.full_name)); // [ 'nodejs/node', 'denoland/deno' ]
// Several at once: keep whatever succeeded
const results = await Promise.allSettled(
["nodejs/node", "no-such-owner/no-such-repo"].map((name) => getJson(`${api}/${name}`))
);
for (const result of results) {
if (result.status === "fulfilled") {
console.log("ok", result.value.full_name); // ok nodejs/node
} else {
console.log("failed", result.reason.message); // failed HTTP 404 from https://api.github.com/...
}
}This runs as a module, where await works at the top level: save it as
repos.mjs and run node repos.mjs, or load it with type="module" in a page.
names.map(...) starts every request before the first await, so they run in
parallel. An await inside a for...of loop would run them one after another. To
check or tidy a JSON response by hand, paste it into the
JSON formatter.
DOM basics, start to finish
A to-do list: one form, one list, one click listener for every item, and the
list saved in localStorage so it survives a reload.
<form id="new-todo">
<input name="title" required placeholder="What needs doing?" />
<button>Add</button>
</form>
<ul id="todos"></ul>
<script type="module">
const form = document.querySelector("#new-todo");
const list = document.querySelector("#todos");
const saved = JSON.parse(localStorage.getItem("todos") ?? "[]");
function render(todos) {
list.replaceChildren(
...todos.map((todo, index) => {
const li = document.createElement("li");
li.textContent = todo.title;
li.dataset.index = index;
li.classList.toggle("done", todo.done);
return li;
})
);
localStorage.setItem("todos", JSON.stringify(todos));
}
form.addEventListener("submit", (event) => {
event.preventDefault();
const { title } = Object.fromEntries(new FormData(form));
saved.push({ title: title.trim(), done: false });
render(saved);
form.reset();
});
list.addEventListener("click", (event) => {
const li = event.target.closest("li");
if (!li) return;
const todo = saved[Number(li.dataset.index)];
todo.done = !todo.done;
render(saved);
});
render(saved);
</script>textContent rather than innerHTML is what makes it safe to show whatever
someone typed. The single listener on the ul handles clicks for items that did
not exist when it was added, which is the point of event delegation.
preventDefault stops the form reloading the page. For the markup side, from
labelling that input to the form attributes and input types these scripts read,
see the HTML cheat sheet.
Gotchas
The mistakes almost everyone makes in their first month of JavaScript.
| Looks right | What actually happens | Do this instead |
|---|---|---|
if (input == 0) | Also true for "", "0" and [] | ===, and x == null only for null or undefined |
typeof value === "object" | Also true for null and arrays | value !== null && typeof value === "object", or Array.isArray |
[10, 9, 1].sort() | [1, 10, 9]: sorted as strings | .sort((a, b) => a - b) |
0.1 + 0.2 === 0.3 | false: the sum is 0.30000000000000004 | Compare with a tolerance, keep money in pennies |
x === NaN | Always false, even when x is NaN | Number.isNaN(x) |
"5" + 3 | "53": joined as strings | Number("5") + 3 |
["1", "2", "3"].map(parseInt) | [1, NaN, NaN]: the index is passed as the base | .map(Number) |
setTimeout(obj.method, 100) | this is lost inside the method | setTimeout(() => obj.method(), 100) |
items.forEach(async (i) => await save(i)) | Does not wait: the code after it runs first | for (const i of items) await save(i), or Promise.all |
const make = () => { name: "Ada" } | Returns undefined: the braces are a function body | () => ({ name: "Ada" }) |
return on its own line, value below | Returns undefined: a semicolon is inserted after return | Start the value on the same line as return |
new Date(2026, 9, 27) | 27 October: months count from 0 | new Date(2026, 8, 27) for September |
{ a: 1 } === { a: 1 } | false: two different objects | Compare the fields, or JSON.stringify for simple data |
const list = []; list.push(1) | Works: const stops reassignment, not changes | Object.freeze if it really must not change |
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i)) | Prints 3 three times | let i, which makes a new i for each pass |
await fetch(url) on a 404 | Resolves, no error | Check res.ok and throw |
Common questions
Which version of JavaScript does this cheat sheet cover?
ECMAScript 2025, the current edition of the standard, with every snippet run on Node.js 24. Anything added after ES2020 names its edition in the notes column. Most of it has worked in every major browser for years; the newest rows, such as RegExp.escape, the Set methods, Promise.try and JSON imports from ES2025, need a browser from 2025 or later.
What is the difference between == and === in JavaScript?
=== checks that two values are the same type and the same value, with no conversion. == converts the types first and then compares, which gives results like 0 == "" being true and "1" == 1 being true. Use === and !== everywhere. The one common exception is x == null, which is true for both null and undefined and nothing else.
Should I use let, const or var?
Use const by default and let when you need to reassign the variable. Avoid var in new code. const only stops the name being pointed at something else; the object or array it holds can still change. var is scoped to the whole function rather than the block, and can be used before the line that declares it, which causes bugs that let and const turn into errors.
How does this work in JavaScript?
In a normal function or method, this is set by how the function is called, not where it is written. Called as user.greet(), this is user. Pass the same method somewhere as a callback, like setTimeout(user.greet, 100), and it is no longer called on user, so this is not user either: it is undefined in strict code, or whatever the caller picks. Arrow functions have no this of their own and use the this of the surrounding code, which is why they fix most callback problems. bind(obj) makes a copy of a function with this fixed.
What is the difference between an arrow function and a normal function?
An arrow function is shorter, returns a single expression without the return keyword, and has no this, arguments or super of its own: it uses the ones from the code around it. It also cannot be called with new. Use arrows for callbacks and short helpers. Use a normal function or method syntax for object methods that need this, and for functions you want to call above the line that defines them.
What is the difference between null and undefined?
undefined means a value was never set: a variable declared without a value, a missing object property, a missing function argument, or a function with no return. null is a value you set on purpose to say there is nothing here. Both are falsy, x == null catches both, and the ?? operator treats both as missing.
Should I use async/await or .then()?
They do the same thing, since await works on promises. async/await reads top to bottom like ordinary code and uses try and catch for errors, so it is the usual choice for new code. then chains still turn up in older code and are handy for a single quick transformation. Either way, run independent work in parallel with Promise.all rather than awaiting each item in turn.
Is JavaScript the same as Java?
No. They are unrelated languages that share part of a name for marketing reasons dating back to 1995. JavaScript is dynamically typed and runs in every web browser and, through Node.js, on servers. Java is a statically typed, compiled language that runs on the Java Virtual Machine. The official name of the JavaScript standard is ECMAScript.
Why is 0.1 + 0.2 not equal to 0.3 in JavaScript?
Every JavaScript number is a 64-bit binary float, and 0.1 and 0.2 have no exact binary form, so the sum is 0.30000000000000004. Python, Java and C# do the same with their floats. Round when you display a value with toFixed or Intl.NumberFormat, compare with a tolerance such as Math.abs(a - b) < Number.EPSILON, and keep money in whole pennies or cents.
