# 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 |