Design Patterns in JavaScript
22 min read
SingletonObserverFactoryModuleProxyIteratorGeneratorsDesign Patterns
Essential JavaScript Design Patterns
1. Singleton Pattern
Ensures a class has only ONE instance.
class Database {
static #instance = null;
constructor(connectionString) {
if (Database.#instance) return Database.#instance;
this.connection = connectionString;
Database.#instance = this;
}
static getInstance(conn) {
if (!Database.#instance) {
Database.#instance = new Database(conn);
}
return Database.#instance;
}
}
const db1 = Database.getInstance("postgres://...");
const db2 = Database.getInstance("mysql://...");
db1 === db2; // true (same instance, first connection wins)2. Observer Pattern (Pub/Sub)
class EventEmitter {
#listeners = new Map();
on(event, callback) {
if (!this.#listeners.has(event)) {
this.#listeners.set(event, new Set());
}
this.#listeners.get(event).add(callback);
return () => this.off(event, callback); // Return unsubscribe function
}
off(event, callback) {
this.#listeners.get(event)?.delete(callback);
}
emit(event, ...args) {
this.#listeners.get(event)?.forEach(cb => cb(...args));
}
}
const bus = new EventEmitter();
const unsub = bus.on("userLogin", (user) => console.log(`Welcome ${user}`));
bus.emit("userLogin", "Alice"); // "Welcome Alice"
unsub(); // Clean up3. Factory Pattern
class NotificationFactory {
static create(type, message) {
switch (type) {
case "email": return new EmailNotification(message);
case "sms": return new SMSNotification(message);
case "push": return new PushNotification(message);
default: throw new Error(`Unknown type: ${type}`);
}
}
}
const notification = NotificationFactory.create("email", "Hello!");
notification.send();4. Module Pattern (with Closures)
const Counter = (() => {
let count = 0; // Private
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count,
};
})();
Counter.increment(); // 1
Counter.getCount(); // 1
Counter.count; // undefined (private!)5. Proxy Pattern
const handler = {
get(target, prop) {
console.log(`Accessing ${prop}`);
return prop in target ? target[prop] : `Property ${prop} not found`;
},
set(target, prop, value) {
if (prop === "age" && typeof value !== "number") {
throw new TypeError("Age must be a number");
}
target[prop] = value;
return true;
}
};
const user = new Proxy({}, handler);
user.name = "Alice";
user.age = "thirty"; // TypeError: Age must be a number6. Iterator Pattern (Generators)
function* range(start, end, step = 1) {
for (let i = start; i < end; i += step) {
yield i;
}
}
for (const num of range(0, 10, 2)) {
console.log(num); // 0, 2, 4, 6, 8
}
// Infinite sequence
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
fib.next().value; // 0
fib.next().value; // 1
fib.next().value; // 1
fib.next().value; // 2Pattern Selection Guide
| Pattern | Use When | Real-World Example |
|---|---|---|
| Singleton | One instance globally | Database connection, Logger |
| Observer | Event-driven communication | Redux store, DOM events |
| Factory | Creating objects by type | UI component factories |
| Module | Encapsulation & privacy | API clients, utilities |
| Proxy | Validation, logging, caching | Vue.js reactivity, ORMs |
| Iterator | Lazy evaluation, sequences | Pagination, data streams |