# JavaScript Advanced Patterns — Deep Dive
> Closures, prototypes, iterators, generators, proxies, and design patterns.
---
## 1. Closures — The Foundation
A closure is a function that retains access to its outer scope's variables.
### Practical: Function Factory
```javascript
function createCounter(initial = 0) {
let count = initial; // Private variable
return {
get() { return count; },
increment(n = 1) { count += n; },
decrement(n = 1) { count -= n; },
reset() { count = initial; },
};
}
const counter = createCounter(0);
counter.increment();
console.log(counter.get()); // 1
// count is not directly accessible — true encapsulation ✅
```
### Practical: Memoization
```javascript
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
const fibonacci = memoize((n) => {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
fibonacci(100); // Instant (without memoization: ~3 seconds)
```
### Practical: Event Handler Cleanup
```javascript
function attachHandler(element, event, handler) {
element.addEventListener(event, handler);
// Return cleanup function
return () => element.removeEventListener(event, handler);
}
const cleanup = attachHandler(button, 'click', handleClick);
// Later...
cleanup(); // Remove listener
```
---
## 2. Prototype Chain
### How Lookup Works
```
instance → prototype → prototype's prototype → ... → Object.prototype → null
```
```javascript
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} makes a sound`; }
}
class Dog extends Animal {
speak() { return `${this.name} barks`; }
}
const dog = new Dog('Rex');
dog.speak(); // "Rex barks" — found on Dog.prototype
dog.toString(); // Found on Object.prototype
```
### Manual Prototype Manipulation
```javascript
function User(name) {
this.name = name;
}
User.prototype.greet = function() {
return `Hello, ${this.name}`;
};
// Add method to all instances retroactively
User.prototype.isAdmin = function() {
return this.name === 'admin';
};
const alice = new User('Alice');
alice.greet(); // "Hello, Alice"
alice.isAdmin(); // false (method added after creation, still works)
```
### `Object.create()` — Prototype Without Constructor
```javascript
const animal = {
speak() { return `${this.name} makes a sound`; },
};
const dog = Object.create(animal);
dog.name = 'Rex';
dog.speak(); // "Rex makes a sound"
// dog.__proto__ === animal ✅
// No constructor called — pure prototype delegation
```
---
## 3. Iterators and Generators
### Custom Iterator
```javascript
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const end = this.to;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
}
return { done: true };
},
};
},
};
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
[...range]; // [1, 2, 3, 4, 5]
```
### Generator Functions
```javascript
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
fib.next(); // { value: 0, done: false }
fib.next(); // { value: 1, done: false }
fib.next(); // { value: 1, done: false }
fib.next(); // { value: 2, done: false }
// Take first 10
const first10 = [];
for (const n of fibonacci()) {
first10.push(n);
if (first10.length === 10) break;
}
```
### `yield*` — Delegate to Another Generator
```javascript
function* evens(n) {
for (let i = 0; i < n; i += 2) yield i;
}
function* odds(n) {
for (let i = 1; i < n; i += 2) yield i;
}
function* allNumbers(n) {
yield* evens(n);
yield* odds(n);
}
[...allNumbers(10)]; // [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
```
### Sending Values Into Generators
```javascript
function* echo() {
let value;
while (true) {
value = yield value * 2;
}
}
const gen = echo();
gen.next(); // { value: undefined, done: false }
gen.next(5); // { value: 10, done: false }
gen.next(3); // { value: 6, done: false }
```
---
## 4. Proxies — Meta-Programming
A Proxy intercepts operations on an object.
### Validation Proxy
```javascript
const userSchema = {
name: 'string',
age: 'number',
email: 'string',
};
const user = new Proxy({}, {
set(target, property, value) {
if (!(property in userSchema)) {
throw new Error(`Unknown property: ${property}`);
}
if (typeof value !== userSchema[property]) {
throw new TypeError(
`Expected ${userSchema[property]} for ${property}, got ${typeof value}`
);
}
target[property] = value;
return true;
},
get(target, property) {
if (!(property in target)) {
return undefined;
}
return target[property];
},
});
user.name = 'Alice'; // ✅
user.age = 30; // ✅
user.age = '30'; // ❌ TypeError
user.phone = '123'; // ❌ Error: Unknown property
```
### Reactive Proxy (Vue-style)
```javascript
function reactive(obj) {
const handlers = new Map();
const proxy = new Proxy(obj, {
set(target, prop, value) {
target[prop] = value;
// Notify watchers
handlers.get(prop)?.forEach(fn => fn(value, target[prop]));
return true;
},
});
return {
get proxy(),
watch(prop, fn) {
if (!handlers.has(prop)) handlers.set(prop, []);
handlers.get(prop).push(fn);
return () => {
const fns = handlers.get(prop);
handlers.set(prop, fns.filter(f => f !== fn));
};
},
};
}
const { proxy, watch } = reactive({ count: 0 });
watch('count', (newVal) => console.log('Count changed to', newVal));
proxy.count = 5; // "Count changed to 5"
```
### Lazy Loading Proxy
```javascript
function lazyLoad(getValue) {
let value;
let loaded = false;
return new Proxy({}, {
get(_, prop) {
if (!loaded) {
value = getValue();
loaded = true;
}
return value[prop];
},
});
}
const api = lazyLoad(async () => {
const res = await fetch('/api/data');
return res.json();
});
```
---
## 5. Design Patterns in JavaScript
### Singleton
```javascript
class Database {
static #instance;
constructor() {
if (Database.#instance) return Database.#instance;
// Initialize connection...
Database.#instance = this;
}
query(sql) { /* ... */ }
}
const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true
```
### Observer / Pub-Sub
```javascript
class EventEmitter {
#events = new Map();
on(event, listener) {
if (!this.#events.has(event)) {
this.#events.set(event, []);
}
this.#events.get(event).push(listener);
// Return unsubscribe function
return () => this.off(event, listener);
}
off(event, listener) {
const listeners = this.#events.get(event);
this.#events.set(
event,
listeners.filter(l => l !== listener)
);
}
emit(event, ...args) {
this.#events.get(event)?.forEach(l => l(...args));
}
}
const emitter = new EventEmitter();
const unsubscribe = emitter.on('data', (d) => console.log(d));
emitter.emit('data', 'Hello!'); // "Hello!"
unsubscribe(); // Stop listening
```
### Module Pattern (IIFE)
```javascript
const Module = (() => {
// Private state
let data = [];
// Private helper
function validate(item) {
return item != null;
}
// Public API
return {
add(item) {
if (validate(item)) data.push(item);
},
getAll() { return [...data]; },
count() { return data.length; },
};
})();
Module.add('item');
Module.getAll(); // ['item']
Module.data; // undefined (private)
```
### Factory Pattern
```javascript
function createShape(type, ...args) {
switch (type) {
case 'circle':
return { type, radius: args[0], area() { return Math.PI * this.radius ** 2; } };
case 'rectangle':
return { type, w: args[0], h: args[1], area() { return this.w * this.h; } };
case 'triangle':
return { type, base: args[0], height: args[1], area() { return this.base * this.height / 2; } };
default:
throw new Error(`Unknown shape: ${type}`);
}
}
const circle = createShape('circle', 5);
circle.area(); // 78.54
```
### Decorator Pattern
```javascript
function withLogging(fn) {
return function(...args) {
console.log(`→ ${fn.name}(${args.join(', ')})`);
const result = fn(...args);
console.log(`← ${fn.name} returned ${result}`);
return result;
};
}
function withRetry(fn, maxRetries = 3) {
return async function(...args) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn(...args);
} catch (e) {
console.warn(`Attempt ${i + 1} failed:`, e.message);
if (i === maxRetries - 1) throw e;
}
}
};
}
// Compose decorators
const fetchUser = withLogging(withRetry(async (id) => {
const res = await fetch(`/api/users/${id}`);
return res.json();
}));
```
---
## 6. JavaScript Gotchas and Dark Patterns
### `this` Context
```javascript
// ❌ Arrow functions don't have their own `this`
const obj = {
name: 'Alice',
greet() { return `Hello, ${this.name}`; },
arrowGreet: () => `Hello, ${this.name}`, // this = window/global, not obj!
};
obj.greet(); // "Hello, Alice"
obj.arrowGreet(); // "Hello, undefined"
// ✅ Use arrow for callbacks where you want parent `this`
const component = {
init() {
button.addEventListener('click', () => {
this.handleClick(); // this = component ✅
});
},
handleClick() { /* ... */ },
};
```
### Type Coercion
```javascript
// Dangerous == comparisons
'' == '0' // false
0 == '' // true
0 == '0' // true
false == 'false' // false
false == '0' // true
null == undefined // true
NaN == NaN // false
// ✅ Always use ===
'' === '0' // false
0 === '' // false
```
### Variable Hoisting
```javascript
console.log(a); // undefined (hoisted, not initialized)
var a = 5;
console.log(b); // ReferenceError (not hoisted)
let b = 5;
// Functions are fully hoisted
hello(); // "Hello!"
function hello() { console.log('Hello!'); }
// Function expressions are NOT hoisted
// goodbye(); // TypeError
const goodbye = function() { console.log('Goodbye!'); };
```
### `for...in` vs `for...of`
```javascript
const arr = ['a', 'b', 'c'];
for (const key in arr) {
console.log(key); // "0", "1", "2" — INDEXES (and inherited properties!)
}
for (const value of arr) {
console.log(value); // "a", "b", "c" — VALUES ✅
}
// ⚠️ for...in on arrays is generally wrong
```
### Array Methods Return Values
```javascript
const arr = [1, 2, 3];
arr.forEach((n) => console.log(n)); // Returns undefined ❌ (common mistake)
arr.map((n) => n * 2); // Returns [2, 4, 6] ✅
arr.filter((n) => n > 1); // Returns [2, 3] ✅
arr.reduce((sum, n) => sum + n, 0); // Returns 6 ✅
arr.some((n) => n > 2); // Returns true ✅
arr.every((n) => n > 0); // Returns true ✅
// forEach returns undefined — don't try to chain!
```
### Object Property Ordering
```javascript
// ES2015+ guarantees:
// 1. Integer keys (numeric, ascending)
// 2. String keys (insertion order)
// 3. Symbol keys (insertion order)
const obj = {
b: 1,
a: 2,
2: 3,
1: 4,
};
Object.keys(obj); // ['1', '2', 'b', 'a']
// Numeric keys first (sorted), then string keys (insertion order)
```
### `setTimeout` with 0ms
```javascript
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// Output: 1, 4, 3, 2
// Microtasks (Promises) run before macrotasks (setTimeout)
```
---
## 7. Event Loop Deep Dive
### Task Queue (Macrotasks)
```
setTimeout, setInterval, setImmediate, I/O callbacks, UI rendering
```
### Microtask Queue (Microtasks)
```
Promise callbacks, queueMicrotask(), MutationObserver
```
### Execution Order
```javascript
console.log('1. Script start');
setTimeout(() => console.log('2. setTimeout'), 0);
Promise.resolve()
.then(() => console.log('3. Promise 1'))
.then(() => console.log('4. Promise 2'));
queueMicrotask(() => console.log('5. Microtask'));
console.log('6. Script end');
// Output: 1, 6, 3, 5, 4, 2
// Script → Microtasks (all) → Next macrotask
```
---
## Quick Reference: Pattern Selection
| Problem | Pattern |
|---------|---------|
| Encapsulation | Closures |
| Cache results | Memoization |
| Event system | Observer / EventEmitter |
| Single instance | Singleton |
| Create objects | Factory |
| Add behavior | Decorator |
| Private state | Module (IIFE) |
| Lazy evaluation | Generators |
| Intercept access | Proxy |
| Custom iteration | Symbol.iterator |
| Debounce/throttle | Closures |
| State management | Reactive Proxy |