JavaScript Cheat Sheet
Every method, pattern, and ES6+ feature. Searchable, filterable by level, copy-ready.
Every method, pattern, and ES6+ feature. Searchable, filterable by level, copy-ready.
Most developers don't memorize everything. They keep a solid JavaScript cheat sheet nearby and look things up when needed. That's not a weakness - that's just how the job works.
JavaScript has a lot of moving parts. Arrow functions, array methods, DOM manipulation, async/await, ES6 destructuring. Keeping all of it in your head at once isn't realistic.
This guide covers the core syntax and methods you'll reach for constantly:
Variables, data types, and type conversion
String and array methods
Loops, conditionals, and functions
ES6+ features like template literals and the spread operator
DOM events and basic error handling
Bookmark it. Use it as your quick reference when you're mid-build and just need to check the syntax fast.
A JavaScript cheat sheet is a condensed syntax reference that puts the core language in one place: variables, data types, functions, arrays, objects, loops, and events.
It's not a tutorial. No step-by-step explanations, no beginner hand-holding.
The point is fast lookup. You're mid-project, you can't remember how array destructuring syntax works, and you need the answer in ten seconds, not a 20-minute read.
Three options: var, let, and const. In practice, var is legacy. You'll mostly see it in older codebases.
Use let when the value needs to change. Use const for everything else, which ends up being most declarations.
var name = 'Alice'; // function-scoped, hoisted
let count = 0; // block-scoped, reassignable
const MAX_SIZE = 100; // block-scoped, not reassignable
|
Keyword |
Scope |
Hoisted |
Reassignable |
|---|---|---|---|
|
var |
Function |
Yes (undefined) |
Yes |
|
let |
Block |
No (TDZ) |
Yes |
|
const |
Block |
No (TDZ) |
No |
TDZ (Temporal Dead Zone): accessing let or const before declaration throws a ReferenceError.
With var, you just get undefined, which is its own kind of tricky.
Seven primitive types. Stored by value, not by reference. That distinction matters a lot once you start passing data between functions.
|
Type |
Example |
Notes |
|---|---|---|
|
string |
|
Immutable. UTF-16 encoded. |
|
number |
|
All numbers are 64-bit floats. NaN is technically a number. |
|
bigint |
|
Integers beyond |
|
boolean |
|
Logic and conditional checks. |
|
undefined |
|
Declared but never assigned. |
|
null |
|
Intentional absence of a value. |
|
symbol |
|
Unique identifiers. Rarely used directly. |
typeof 'hello' // 'string'
typeof 42 // 'number'
typeof true // 'boolean'
typeof undefined // 'undefined'
typeof null // 'object' <- known JS quirk
typeof Symbol() // 'symbol'
typeof 42n // 'bigint'
Objects, Arrays, and Functions are reference types. Assign one to a new variable and both variables point to the same place in memory, not a copy.
const a = { x: 1 };
const b = a;
b.x = 99;
console.log(a.x); // 99 - same object in memory
This trips up beginners constantly. To get an actual copy of an object, use the spread operator or Object.assign().
|
Operator |
Description |
Example |
Result |
|---|---|---|---|
|
|
Addition |
|
|
|
|
Subtraction |
|
|
|
|
Multiplication |
|
|
|
|
Division |
|
|
|
|
Modulus (remainder) |
|
|
|
|
Exponentiation |
|
|
|
|
Increment |
|
|
|
|
Decrement |
|
|
Watch out with +: if either operand is a string, it concatenates instead of adding. '5' + 3 gives '53', not 8. Classic JS gotcha.
Always prefer === over == in production code. The loose equality operator performs type coercion, which produces genuinely confusing results.
|
Operator |
Meaning |
Example |
Result |
|---|---|---|---|
|
|
Loose equal (coerces type) |
|
|
|
|
Strict equal (no coercion) |
|
|
|
|
Loose not equal |
|
|
|
|
Strict not equal |
|
|
|
|
Greater than |
|
|
|
|
Less than |
|
|
|
|
Greater or equal |
|
|
|
|
Less or equal |
|
|
|
Operator |
Name |
Description |
Example |
|---|---|---|---|
|
|
AND |
True if both operands are truthy |
|
|
|
OR |
True if at least one operand is truthy |
|
|
|
NOT |
Inverts the boolean value |
|
Short-circuit evaluation: with &&, if the left side is falsy, the right side never runs.
With ||, if the left side is truthy, the right side is skipped. Useful for default values and conditional execution.
Both were added in ES2020. Both solve real problems that drove developers nuts for years.
// ?? returns the right side only when left is null or undefined
const port = userConfig.port ?? 3000;
// || would return 3000 even for port = 0 (falsy)
// ?? only triggers for null/undefined - much safer
// ?. stops execution if a property is null or undefined
const city = user?.address?.city;
// No TypeError if user or address doesn't exist. Just returns undefined.
Strings are immutable in JavaScript. Every method returns a new string. The original is never changed.
|
Method |
What it does |
Example |
|---|---|---|
|
|
Returns character count |
|
|
|
Converts to uppercase |
|
|
|
Converts to lowercase |
|
|
|
Removes whitespace from both ends |
|
|
|
Splits into array by separator |
|
|
|
Replaces first match |
|
|
|
Replaces all matches |
|
|
|
Returns true if string contains str |
|
|
|
Returns first index of str, or -1 |
|
|
|
Extracts characters from index s to e |
|
|
|
Similar to slice, no negative indexes |
|
|
|
Pads string start to length n |
|
|
|
Repeats the string n times |
|
|
|
Checks if string starts with str |
|
|
|
Checks if string ends with str |
|
|
|
Supports negative indexing |
|
|
|
Returns UTF-16 code at index i |
|
Template literals use backticks instead of quotes. They support string interpolation, multi-line strings, and tagged templates.
const name = 'Alice';
const age = 30;
// Interpolation with ${}
const greeting = `Hello, ${name}. You are ${age} years old.`;
// Multi-line strings - newlines are preserved as-is
const html = `
<div>
<p>${name}</p>
</div>
`;
// Expressions inside ${}
const msg = `Next year you'll be ${age + 1}`;
Template literals are cleaner for anything beyond a simple two-part join. Concatenation gets unreadable fast.
// Old way
const msg1 = 'Hello, ' + name + '. You are ' + age + ' years old.';
// Modern way
const msg2 = `Hello, ${name}. You are ${age} years old.`;
The array literal syntax is what you'll use 99% of the time. new Array() exists but there's almost never a reason to reach for it.
// Array literal (preferred)
const fruits = ['apple', 'banana', 'cherry'];
// Accessing by index (zero-based)
fruits[0] // 'apple'
fruits[2] // 'cherry'
fruits.at(-1) // 'cherry' - negative indexing, ES2022
// Length
fruits.length // 3
// Arrays can hold mixed types
const mixed = [1, 'two', true, null, { x: 3 }];
These change the original array directly.
|
Method |
What it does |
Returns |
|---|---|---|
|
|
Adds items to the end |
New length |
|
|
Removes last item |
Removed item |
|
|
Removes first item |
Removed item |
|
|
Adds items to the start |
New length |
|
|
Removes n items at index i, optionally inserts a |
Removed items |
|
|
Sorts in place (default: alphabetical) |
Sorted array |
|
|
Reverses in place |
Reversed array |
|
|
Fills index s to e with val |
Modified array |
.sort() defaults to alphabetical order, which means numbers sort wrong without a comparator.
[10, 9, 2].sort() gives [10, 2, 9]. Always pass a function for numeric sorting: arr.sort((a, b) => a - b).
These return a new array and leave the original untouched. Prefer these in most cases.
|
Method |
What it does |
Returns |
|---|---|---|
|
|
Transforms each item via fn |
New array (same length) |
|
|
Keeps items where fn returns true |
New array (shorter) |
|
|
Accumulates values into a single result |
Single value |
|
|
Returns first item where fn is true |
Item or undefined |
|
|
Returns index of first match |
Index or -1 |
|
|
True if at least one item passes fn |
Boolean |
|
|
True if all items pass fn |
Boolean |
|
|
Flattens nested arrays |
New flat array |
|
|
Maps then flattens one level |
New array |
|
|
Merges arrays |
New merged array |
|
|
Extracts section from s to e |
New array |
|
|
Checks if item exists |
Boolean |
|
|
Returns first index of item |
Index or -1 |
|
|
Joins items into a string |
String |
|
|
Runs fn for each item, no return value |
undefined |
const nums = [1, 2, 3, 4, 5];
nums.map(n => n * 2) // [2, 4, 6, 8, 10]
nums.filter(n => n % 2 === 0) // [2, 4]
nums.reduce((acc, n) => acc + n, 0) // 15
nums.find(n => n > 3) // 4
nums.some(n => n > 4) // true
nums.every(n => n > 0) // true
Array destructuring lets you pull values out by position and assign them to variables in one line.
const [first, second, third] = ['a', 'b', 'c'];
// first = 'a', second = 'b', third = 'c'
// Skipping elements
const [, , thirdOnly] = ['a', 'b', 'c'];
// thirdOnly = 'c'
// Default values
const [x = 10, y = 20] = [5];
// x = 5, y = 20
// Rest pattern - collects remaining items
const [head, ...tail] = [1, 2, 3, 4];
// head = 1, tail = [2, 3, 4]
// Swapping variables without a temp variable
let a = 1, b = 2;
[a, b] = [b, a];
// a = 2, b = 1
The object literal is the standard. new Object() and Object.create() exist but you'll rarely need them outside specific inheritance patterns.
// Object literal (standard)
const user = {
name: 'Alice',
age: 30,
isAdmin: false
};
// Constructor function
function User(name, age) {
this.name = name;
this.age = age;
}
const alice = new User('Alice', 30);
// Object.create() - sets prototype explicitly
const proto = { greet() { return `Hi, I'm ${this.name}`; } };
const bob = Object.create(proto);
bob.name = 'Bob';
Dot notation is standard. Use bracket notation when the property name is dynamic or contains special characters.
const user = { name: 'Alice', age: 30 };
// Dot notation
user.name // 'Alice'
// Bracket notation
user['age'] // 30
const key = 'name';
user[key] // 'Alice'
// Adding a property
user.email = 'alice@example.com';
// Updating a property
user.age = 31;
// Deleting a property
delete user.isAdmin;
|
Method |
What it does |
|---|---|
|
|
Returns array of own property names |
|
|
Returns array of own property values |
|
|
Returns array of |
|
|
Copies properties from src into target |
|
|
Prevents any changes to the object |
|
|
Checks if object is frozen |
|
|
True if obj has the key as own property |
|
|
Creates object from |
Object destructuring pulls named properties into variables.
Unlike array destructuring, order doesn't matter. You match by property name.
const user = { name: 'Alice', age: 30, city: 'NYC' };
// Basic destructuring
const { name, age } = user;
// Renaming variables
const { name: userName, age: userAge } = user;
// Default values
const { role = 'viewer' } = user;
// role = 'viewer' (not in user object)
// Nested destructuring
const { address: { city, zip } } = { address: { city: 'NYC', zip: '10001' } };
// Rest pattern
const { name: n, ...rest } = user;
// rest = { age: 30, city: 'NYC' }
Both create shallow copies. The spread syntax is cleaner for most use cases.
const original = { a: 1, b: 2 };
// Spread (ES2018)
const copy1 = { ...original, c: 3 };
// { a: 1, b: 2, c: 3 }
// Object.assign
const copy2 = Object.assign({}, original, { c: 3 });
// { a: 1, b: 2, c: 3 }
Shallow copy means nested objects are still referenced, not duplicated. If you need a deep clone, use structuredClone() (ES2022) or JSON.parse(JSON.stringify(obj)) for simple data.
Function declarations are hoisted. You can call them before they appear in the file.
Function expressions are not hoisted. Call them before the declaration and you get a TypeError.
// Declaration - hoisted
function greet(name) {
return `Hello, ${name}`;
}
// Expression - not hoisted
const greet = function(name) {
return `Hello, ${name}`;
};
// Named function expression
const greet = function greetUser(name) {
return `Hello, ${name}`;
};
Arrow functions have shorter syntax and don't bind their own this. That second point is the one that actually matters.
// Standard function
function add(a, b) { return a + b; }
// Arrow - multiple params
const add = (a, b) => a + b;
// Arrow - single param, no parens needed
const double = n => n * 2;
// Arrow - no params
const greet = () => 'Hello';
// Arrow - block body (needs explicit return)
const add = (a, b) => {
const result = a + b;
return result;
};
Avoid arrow functions as object methods or constructors. this won't point where you expect.
// Default parameters
function greet(name = 'stranger', greeting = 'Hello') {
return `${greeting}, ${name}`;
}
greet() // 'Hello, stranger'
greet('Alice') // 'Hello, Alice'
// Rest parameters - collects remaining args into an array
function sum(...nums) {
return nums.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3, 4) // 10
Rest must be the last parameter. function fn(a, b, ...rest) is valid. function fn(...rest, a) throws a SyntaxError.
A higher-order function either takes a function as an argument or returns one. Most array methods are higher-order functions.
// Passing a function as an argument
const nums = [1, 2, 3];
const doubled = nums.map(n => n * 2); // [2, 4, 6]
// Returning a function (factory pattern)
function multiplier(factor) {
return n => n * factor;
}
const triple = multiplier(3);
triple(5); // 15
Callbacks work fine for simple async tasks. They get messy fast when you need to chain multiple async operations.
// Callback
fs.readFile('file.txt', (err, data) => {
if (err) throw err;
console.log(data);
});
// Promise - cleaner chaining
fetch('/api/user')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
const score = 75;
if (score >= 90) {
console.log('A');
} else if (score >= 70) {
console.log('C');
} else {
console.log('F');
}
// Ternary - one-liner for simple conditions
const grade = score >= 70 ? 'Pass' : 'Fail';
Use switch when you're comparing one value against many possible matches. More readable than a long else if chain.
const day = 'Monday';
switch (day) {
case 'Monday':
case 'Tuesday':
console.log('Weekday');
break;
case 'Saturday':
case 'Sunday':
console.log('Weekend');
break;
default:
console.log('Unknown');
}
Always add break. Without it, execution falls through to the next case. Sometimes useful, usually a bug.
Standard loop. Use when you need the index or a fixed number of iterations.
for (let i = 0; i < 5; i++) {
console.log(i); // 0, 1, 2, 3, 4
}
Cleanest way to iterate over arrays, strings, maps, and sets. No index access.
const fruits = ['apple', 'banana', 'cherry'];
for (const fruit of fruits) {
console.log(fruit);
}
// Works on strings too
for (const char of 'hello') {
console.log(char); // h, e, l, l, o
}
Iterates over the enumerable properties of an object. Not recommended for arrays.
const user = { name: 'Alice', age: 30 };
for (const key in user) {
console.log(`${key}: ${user[key]}`);
}
// name: Alice
// age: 30
Avoid using for...in on arrays. It can pick up inherited prototype properties and the order isn't guaranteed.
// while - checks condition before each iteration
let i = 0;
while (i < 3) {
console.log(i);
i++;
}
// do...while - runs at least once, checks after
let j = 0;
do {
console.log(j);
j++;
} while (j < 3);
break exits the loop entirely. continue skips the current iteration and moves to the next.
for (let i = 0; i < 10; i++) {
if (i === 5) break; // stops at 5
if (i % 2 === 0) continue; // skips even numbers
console.log(i); // 1, 3
}
In nested loops, break and continue only affect the innermost loop. Use labeled statements if you need to break out of an outer loop.
Classes in JavaScript are syntactic sugar over the prototype system. The underlying mechanism is the same, the syntax is just cleaner.
class Animal {
constructor(name, sound) {
this.name = name;
this.sound = sound;
}
speak() {
return `${this.name} says ${this.sound}`;
}
}
const dog = new Animal('Dog', 'woof');
dog.speak(); // 'Dog says woof'
Use extends to inherit from a parent class. Call super() inside the constructor before accessing this.
class Dog extends Animal {
constructor(name) {
super(name, 'woof'); // calls Animal constructor
this.type = 'domestic';
}
fetch(item) {
return `${this.name} fetches the ${item}`;
}
}
const rex = new Dog('Rex');
rex.speak(); // 'Rex says woof' (inherited)
rex.fetch('ball'); // 'Rex fetches the ball'
Static members belong to the class itself, not to instances. Use them for utility functions that don't need instance data.
class MathHelper {
static PI = 3.14159;
static square(n) {
return n * n;
}
}
MathHelper.square(4); // 16
MathHelper.PI; // 3.14159
// Calling on an instance throws TypeError
const m = new MathHelper();
m.square(4); // TypeError
Classes and constructor functions produce the same result. Classes are just cleaner to read and write.
// Constructor function
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return `Hi, I'm ${this.name}`;
};
// Class equivalent
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hi, I'm ${this.name}`;
}
}
A Promise represents a value that isn't available yet. It's either pending, fulfilled, or rejected.
const promise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve('Data loaded');
} else {
reject(new Error('Something failed'));
}
});
promise
.then(data => console.log(data)) // 'Data loaded'
.catch(err => console.error(err))
.finally(() => console.log('Done')); // always runs
async/await is built on top of Promises. It makes async code read like synchronous code.
async function fetchUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
const data = await res.json();
return data;
} catch (err) {
console.error('Request failed:', err);
}
}
An async function always returns a Promise, even if you return a plain value. await can only be used inside an async function (or at the top level of a module in modern JS).
|
Method |
Behavior |
|---|---|
|
|
Resolves when all resolve. Rejects if any one rejects. |
|
|
Resolves or rejects as soon as the first one settles. |
|
|
Waits for all, returns each result regardless of outcome. |
|
|
Resolves as soon as any one resolves. Ignores rejections. |
const [user, posts] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]);
Promise.all is the one you'll use most. Good for parallel requests where you need all results.
try {
const data = JSON.parse(invalidJSON);
} catch (err) {
console.error(err.message);
} finally {
console.log('Always runs - cleanup goes here');
}
finally runs whether or not an error was thrown. Use it for cleanup like closing connections or hiding loading spinners.
|
Error Type |
When it appears |
|---|---|
|
|
Invalid JavaScript syntax (caught at parse time) |
|
|
Operation on wrong type ( |
|
|
Accessing an undeclared variable |
|
|
Value outside allowed range ( |
|
|
Malformed URI in |
|
|
Issues with |
TypeError and ReferenceError are the ones you'll see most often in day-to-day JavaScript development.
Extend the built-in Error class to create custom error types with specific names and behavior.
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
try {
throw new ValidationError('Email is required', 'email');
} catch (err) {
if (err instanceof ValidationError) {
console.log(`${err.name} on field: ${err.field}`);
}
}
ES modules are the standard module system. Supported natively in browsers and Node.js.
// math.js - named exports
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
// Default export - one per file
export default function multiply(a, b) { return a * b; }
// main.js - importing
import multiply, { PI, add } from './math.js';
// Import everything as a namespace
import * as Math from './math.js';
Math.add(2, 3); // 5
// Rename on import
import { add as sum } from './math.js';
import() loads a module on demand instead of upfront. Useful for lazy loading large features or routes.
// Static import (loads at startup)
import { heavyFunction } from './heavy.js';
// Dynamic import (loads only when needed)
async function loadChart() {
const { renderChart } = await import('./chart.js');
renderChart(data);
}
button.addEventListener('click', loadChart);
Returns a Promise. Works anywhere, not just at the top level of a file.
// Single element
document.getElementById('header')
document.querySelector('.nav-link') // first match
document.querySelector('#app > div') // CSS selector
// Multiple elements
document.querySelectorAll('.card') // NodeList
document.getElementsByClassName('btn') // HTMLCollection
document.getElementsByTagName('p') // HTMLCollection
querySelector and querySelectorAll accept any valid CSS selector. Use these over the older methods in most cases.
const el = document.querySelector('#title');
// Content
el.textContent = 'Hello'; // plain text, safe
el.innerHTML = '<b>Hello</b>'; // parses HTML - careful with user input
// Attributes
el.setAttribute('data-id', '42');
el.getAttribute('data-id'); // '42'
el.removeAttribute('data-id');
// Inline styles
el.style.color = 'red';
el.style.fontSize = '18px';
// Classes
el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('open');
el.classList.contains('active'); // true/false
// Create
const div = document.createElement('div');
div.textContent = 'New element';
div.classList.add('card');
// Insert
document.body.appendChild(div);
parent.insertBefore(div, referenceNode);
parent.append(div); // modern, accepts strings too
parent.prepend(div);
// Remove
div.remove(); // modern
parent.removeChild(div); // older API
const btn = document.querySelector('#submit');
// Add listener
btn.addEventListener('click', handleClick);
function handleClick(event) {
event.preventDefault();
console.log('Clicked:', event.target);
}
// Remove listener - must use the same function reference
btn.removeEventListener('click', handleClick);
// Common event types
// click, dblclick, mouseenter, mouseleave
// keydown, keyup, keypress
// input, change, submit, focus, blur
// load, DOMContentLoaded, resize, scroll
Use event.preventDefault() to stop default browser behavior (form submission, link navigation).
Use event.stopPropagation() to prevent the event from bubbling up to parent elements.
Symbols are unique, immutable identifiers. Two symbols with the same description are never equal.
const id1 = Symbol('id');
const id2 = Symbol('id');
id1 === id2 // false - always unique
// Common use: unique object keys that won't clash
const KEY = Symbol('key');
obj[KEY] = 'value';
WeakMap and WeakSet hold weak references. If the key (WeakMap) or value (WeakSet) is garbage collected, the entry disappears automatically.
const cache = new WeakMap();
function process(obj) {
if (cache.has(obj)) return cache.get(obj);
const result = heavyComputation(obj);
cache.set(obj, result);
return result;
}
Good for caching or metadata storage tied to object lifetimes. Not iterable.
A generator function returns an iterator that produces values on demand. Uses function* syntax and yield.
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
const nums = range(1, 5);
nums.next() // { value: 1, done: false }
nums.next() // { value: 2, done: false }
// Use with for...of
for (const n of range(1, 3)) {
console.log(n); // 1, 2, 3
}
Proxy intercepts and redefines operations on an object. Reflect provides the default behavior for those same operations.
const handler = {
get(target, key) {
return key in target ? target[key] : `Property "${key}" not found`;
},
set(target, key, value) {
if (typeof value !== 'number') throw new TypeError('Numbers only');
target[key] = value;
return true;
}
};
const proxy = new Proxy({}, handler);
proxy.x = 10;
proxy.x // 10
proxy.missing // 'Property "missing" not found'
Introduced in ES2021. Combine a logical operation with assignment in one expression.
// ??= assigns only if left side is null or undefined
user.name ??= 'Anonymous';
// ||= assigns only if left side is falsy
config.timeout ||= 3000;
// &&= assigns only if left side is truthy
user.isAdmin &&= checkPermissions(user);
Implicit coercion happens automatically. Explicit conversion is intentional, using Number(), String(), Boolean().
// Implicit - JS converts types for you
'5' + 2 // '52' (number → string)
'5' - 2 // 3 (string → number)
true + 1 // 2 (boolean → number)
// Explicit - you control the conversion
Number('42') // 42
Number(true) // 1
Number(null) // 0
Number('') // 0
Number('abc') // NaN
String(42) // '42'
Boolean(0) // false
Boolean('hi') // true
Every value in JavaScript is either truthy or falsy in a boolean context.
Falsy values (all 8 of them):
false
0
-0
0n // BigInt zero
'' // empty string
null
undefined
NaN
Everything else is truthy. [], {}, '0', and 'false' are all truthy. That last one trips people up.
null == undefined is true but null === undefined is falseLoose equality (==) treats null and undefined as equal to each other, and only to each other. No other value is loosely equal to null.
null == undefined // true
null === undefined // false (different types)
null == 0 // false
null == '' // false
null == false // false
Practical rule: use == null as a shorthand to check for both null and undefined at once.
Variables declared with var at the top level go on the global object (window in browsers).
let and const at the top level don't attach to window, but they're still globally accessible within the module.
var globalVar = 'I am global'; // window.globalVar in browsers
function localScope() {
var localVar = 'only inside function';
let blockLet = 'also only inside function';
}
{
let blockScoped = 'only inside this block';
var notBlockScoped = 'leaks out of block';
}
// blockScoped - ReferenceError
// notBlockScoped - accessible (var ignores blocks)
A closure is a function that retains access to its outer scope even after the outer function has returned.
function counter() {
let count = 0;
return {
increment() { count++; },
decrement() { count--; },
value() { return count; }
};
}
const c = counter();
c.increment();
c.increment();
c.value(); // 2
// count is private - not accessible from outside
The classic loop variable capture bug. Every callback ends up using the final value of i.
// Bug - all log 3
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Fix 1 - use let (block scoped, new binding per iteration)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2
}
// Fix 2 - IIFE to capture the value
for (var i = 0; i < 3; i++) {
((j) => setTimeout(() => console.log(j), 100))(i);
}
this Keywordthis Works in Different Contextsthis is determined by how a function is called, not where it's defined. That's the thing most people get wrong at first.
|
Context |
|
|---|---|
|
Global scope (non-strict) |
|
|
Global scope (strict mode) |
|
|
Object method |
The object the method is called on |
|
Constructor ( |
The newly created instance |
|
Arrow function |
Inherited from surrounding lexical scope |
|
Event listener |
The element that fired the event |
const user = {
name: 'Alice',
greet() {
console.log(this.name); // 'Alice'
},
greetArrow: () => {
console.log(this.name); // undefined - arrow inherits outer this
}
};
All three let you explicitly set what this refers to inside a function.
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const user = { name: 'Alice' };
// call - args passed individually
greet.call(user, 'Hello', '!'); // 'Hello, Alice!'
// apply - args passed as array
greet.apply(user, ['Hello', '!']); // 'Hello, Alice!'
// bind - returns new function with this permanently set
const greetAlice = greet.bind(user);
greetAlice('Hi', '.'); // 'Hi, Alice.'
bind is useful when passing methods as callbacks. Without it, this gets lost when the function is called outside its object context.
A solid JavaScript syntax reference covers variables, data types, operators, arrays, objects, functions, loops, classes, promises, and ES6+ features.
The more useful ones also include code examples, not just syntax listings.
== and === in JavaScript?== uses type coercion, so '5' == 5 returns true.
Strict equality (===) compares both value and type with no conversion. '5' === 5 returns false. Use === by default.
.map(), .filter(), and .reduce() are the three you'll use constantly.
After those, .find(), .some(), .every(), and .flat() cover most real-world needs. All are non-mutating - they return new arrays.
let, const, and var?var is function-scoped and hoisted. let and const are block-scoped and not hoisted.
Use const by default, let when the value needs to change. Avoid var in modern JavaScript.
this mean in JavaScript?this refers to the object that called the function. It changes depending on the call context.
In arrow functions, this is inherited from the surrounding scope. In event listeners, it refers to the element that fired the event.
A closure is a function that keeps access to variables from its outer scope even after that scope has closed.
Common uses include private variables, factory functions, and callback patterns. The counter function pattern is the most frequently cited example.
null and undefined?undefined means a variable was declared but never assigned a value.
null is an intentional assignment - it signals the deliberate absence of a value. Both are falsy, but typeof null returns 'object', which is a known JavaScript quirk.
Template literals use backticks and support string interpolation via ${}, multi-line strings, and embedded expressions.
They replaced messy string concatenation in ES6. \Hello, ${nameis cleaner and easier to read than'Hello, ' + name.
.map() and .forEach()?.map() returns a new array with transformed values. .forEach() returns undefined - it just runs a function on each item.
Use .map() when you need the result. Use .forEach() when you only need the side effect.
async/await is syntax built on top of Promises that makes asynchronous code read like synchronous code.
An async function always returns a Promise. await pauses execution until the Promise resolves, and errors are handled with standard try/catch blocks.