AiTechWorlds
AiTechWorlds
Design patterns are proven solutions to recurring problems. In JavaScript, a small set of patterns comes up constantly in frameworks, libraries, and production codebases. Recognizing them makes unfamiliar code immediately understandable.
Components subscribe to events and get notified when they fire without tight coupling:
class EventEmitter {
#listeners = new Map();
on(event, listener) {
if (!this.#listeners.has(event)) {
this.#listeners.set(event, new Set());
}
this.#listeners.get(event).add(listener);
return () => this.off(event, listener); // return unsubscribe function
}
off(event, listener) {
this.#listeners.get(event)?.delete(listener);
}
emit(event, ...args) {
this.#listeners.get(event)?.forEach(listener => listener(...args));
}
once(event, listener) {
const wrapper = (...args) => {
listener(...args);
this.off(event, wrapper);
};
return this.on(event, wrapper);
}
}
// Usage
const store = new EventEmitter();
const unsubscribe = store.on("userLoggedIn", (user) => {
updateNavbar(user);
loadDashboard(user.id);
});
store.emit("userLoggedIn", { id: 42, name: "Alice" });
// Later: clean up
unsubscribe();Ensure only one instance exists used for caches, connection pools, app state:
class AppConfig {
static #instance = null;
#settings = {};
constructor() {
if (AppConfig.#instance) {
return AppConfig.#instance; // return existing instance
}
AppConfig.#instance = this;
this.#settings = { theme: "light", language: "en" };
}
static getInstance() {
if (!AppConfig.#instance) {
new AppConfig();
}
return AppConfig.#instance;
}
get(key) { return this.#settings[key]; }
set(key, value) { this.#settings[key] = value; }
}
const config1 = AppConfig.getInstance();
const config2 = AppConfig.getInstance();
config1 === config2; // true — same object
// Module-level singleton (simpler, common in real code)
export const config = {
theme: "light",
language: "en"
};Create objects without specifying the exact class:
// Simple factory function
function createUser(type, data) {
const base = {
...data,
createdAt: new Date(),
isActive: true
};
switch (type) {
case "admin":
return { ...base, permissions: ["read", "write", "delete"], dashboard: "admin" };
case "editor":
return { ...base, permissions: ["read", "write"], dashboard: "editor" };
case "viewer":
return { ...base, permissions: ["read"], dashboard: "viewer" };
default:
throw new Error(`Unknown user type: ${type}`);
}
}
const admin = createUser("admin", { name: "Alice", email: "alice@example.com" });Define a family of algorithms and switch between them at runtime:
const sortStrategies = {
bubble(arr) {
const a = [...arr];
for (let i = 0; i < a.length; i++)
for (let j = 0; j < a.length - i - 1; j++)
if (a[j] > a[j + 1]) [a[j], a[j + 1]] = [a[j + 1], a[j]];
return a;
},
quick(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[Math.floor(arr.length / 2)];
const left = arr.filter(x => x < pivot);
const mid = arr.filter(x => x === pivot);
const right = arr.filter(x => x > pivot);
return [...this.quick(left), ...mid, ...this.quick(right)];
},
builtin(arr) {
return [...arr].sort((a, b) => a - b);
}
};
function sort(arr, strategy = "builtin") {
return sortStrategies[strategy](arr);
}
// Real-world: payment strategies
const paymentProcessors = {
stripe: async (amount, card) => stripeAPI.charge(amount, card),
paypal: async (amount, email) => paypalAPI.pay(amount, email),
crypto: async (amount, wallet) => cryptoAPI.transfer(amount, wallet)
};
async function processPayment(method, ...args) {
const processor = paymentProcessors[method];
if (!processor) throw new Error(`Unknown payment method: ${method}`);
return processor(...args);
}Intercept and customize operations on objects:
// Validation proxy
function createValidatedObject(target, validators) {
return new Proxy(target, {
set(obj, key, value) {
if (validators[key]) {
const error = validators[key](value);
if (error) throw new Error(`${key}: ${error}`);
}
obj[key] = value;
return true;
}
});
}
const user = createValidatedObject({}, {
age: v => v < 0 ? "Must be positive" : v > 150 ? "Unrealistic age" : null,
email: v => !v.includes("@") ? "Invalid email" : null
});
user.age = 25; // OK
user.age = -5; // Error: age: Must be positive
user.email = "alice@example.com"; // OKAdd behavior to objects without changing their class JavaScript decorators (@) do this at the syntax level:
// Manual decorator (wrapper function)
function withLogging(fn, name = fn.name) {
return function(...args) {
console.time(name);
try {
const result = fn.apply(this, args);
return result;
} finally {
console.timeEnd(name);
}
};
}
const expensiveOp = withLogging(function expensiveOp(n) {
return fibonacci(n);
});
// Method decorators (Stage 3 proposal)
class Api {
@retry({ times: 3, delay: 1000 })
@cache({ ttl: 60 })
async getUser(id) {
return fetch(`/api/users/${id}`).then(r => r.json());
}
}const cartModule = (() => {
// Private
let items = [];
function calculate() {
return items.reduce((sum, i) => sum + i.price * i.qty, 0);
}
// Public
return {
add: (item) => { items.push(item); },
remove: (id) => { items = items.filter(i => i.id !== id); },
total: calculate,
clear: () => { items = []; }
};
})();
cartModule.add({ id: 1, price: 9.99, qty: 2 });
cartModule.total(); // 19.98Next lesson: Node.js JavaScript on the server side.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises