Closures
- A closure is a function that retains access to its outer scope after the outer function returns.
- Created every time a function is created, at function creation time.
- Key use cases: data encapsulation, factory functions, memoization, event handlers with state.
- Classic gotcha:
var in for-loops shares the binding. Fix with let or IIFE.
function counter() {
let n = 0;
return {
inc: () => ++n,
val: () => n,
};
}
const c = counter();
c.inc(); c.inc();
c.val(); // 2
Event Loop
- JavaScript is single-threaded with a non-blocking event loop.
- Call stack → Microtasks (all) → Next macrotask → Microtasks → ...
- Microtasks: Promise callbacks, queueMicrotask, MutationObserver.
- Macrotasks: setTimeout, setInterval, I/O, UI rendering.
console.log('1');
setTimeout(() => console.log('2'), 0); // macro
Promise.resolve().then(() => console.log('3')); // micro
console.log('4');
// Output: 1 → 4 → 3 → 2
Promises & async/await
- Promise states: pending → fulfilled / rejected. Immutable once settled.
- Combinators: Promise.all (all resolve), .race (first settles), .allSettled (all done), .any (first resolves).
- async function always returns a Promise. await pauses execution of that function only.
- Always handle rejections — unhandled rejections crash Node.js.
// Sequential (slow)
const a = await fetchA();
const b = await fetchB();
// Parallel (fast ~2x)
const [a, b] = await Promise.all([
fetchA(),
fetchB(),
]);
this Keyword
- this is determined by HOW a function is called, not where defined.
- Global: window (browser) or undefined (strict mode).
- Method call: the object before the dot.
- new: the new object. call/apply/bind: explicit. Arrow: lexical outer scope.
const obj = {
val: 42,
regular() { return this.val; },
arrow: () => this.val, // undefined
};
const fn = obj.regular;
fn(); // undefined (lost context)
fn.call(obj); // 42
Prototypal Inheritance
- Every object has [[Prototype]]. Lookup walks chain until null.
- Object.create(proto) — creates object with proto as prototype.
- class syntax is syntactic sugar over prototypes.
- Use hasOwnProperty() to check own vs inherited properties.
const animal = {
speak() { return '...'; }
};
const dog = Object.create(animal);
dog.name = 'Rex';
dog.speak(); // walks chain → '...'
var / let / const
- var: function-scoped, hoisted (initialized as undefined), redeclarable.
- let: block-scoped, hoisted but in Temporal Dead Zone, not redeclarable.
- const: block-scoped, binding is immutable (value can still mutate if object).
- Default to const. Use let only when reassignment is needed. Avoid var.
var x = 1; // function scope, leaks
let y = 2; // block scope
const z = []; // binding locked
z.push(1); // ✅ value mutable
z = []; // ❌ TypeError
Array Methods
- map — transform each element, returns new array of same length.
- filter — keep matching elements, returns smaller array.
- reduce — accumulate into any value.
- find / findIndex — first match. flat / flatMap — flatten. every / some — boolean tests.
const nums = [1,2,3,4,5];
nums.map(n => n * 2); // [2,4,6,8,10]
nums.filter(n => n > 2); // [3,4,5]
nums.reduce((s,n) => s+n, 0); // 15
nums.find(n => n > 3); // 4
Hoisting
- function declarations: fully hoisted (callable before declaration).
- var declarations: hoisted, initialized as undefined.
- let/const: hoisted but in Temporal Dead Zone until declaration line.
- function expressions and arrow functions: NOT hoisted (follow var/let/const rules).
greet(); // ✅ works
function greet() { ... }
sayHi(); // ❌ TypeError
var sayHi = () => {};
log(x); // ❌ ReferenceError (TDZ)
let x = 5;
Destructuring & Spread
- Array destructuring: const [a, b] = [1, 2];
- Object destructuring: const { name, age = 0 } = person;
- Rest: const { x, ...rest } = obj; — collects remaining props.
- Spread: merging objects, cloning arrays, passing args.
// Rename + default
const { name: n, age = 18 } = user;
// Swap variables
let a = 1, b = 2;
[a, b] = [b, a];
// Merge objects
const merged = { ...defaults, ...overrides };
Debounce vs Throttle
- Debounce: fires AFTER the user stops triggering. Best for: search, resize, form validation.
- Throttle: fires at most once per interval. Best for: scroll, mousemove.
- Mnemonic: Debounce = waits for storm to pass. Throttle = steady drip.
function debounce(fn, delay) {
let t;
return (...a) => {
clearTimeout(t);
t = setTimeout(() => fn(...a), delay);
};
}
const onSearch = debounce(fetch, 300);