JavaScript Design Patterns
1. Module Patterns and Namespace Management
Module Pattern Types
Pattern
Purpose
Usage
IIFE Module
Encapsulation with closure
Pre-ES6 modules
Revealing Module
Explicit public API
Clear interface definition
ES6 Modules
Native import/export
Modern JavaScript standard
CommonJS
Node.js modules
require/module.exports
Namespace Object
Group related functionality
Avoid global pollution
ES6 Module Syntax
Syntax
Description
Example
export
Named export
export const fn = () => {}
export default
Default export
export default class {}
import
Import named exports
import {fn} from './module'
import default
Import default export
import MyClass from './module'
import * as
Import namespace
import * as utils from './utils'
export {}
Re-export
export {fn} from './module'
Module Benefits
Benefit
Description
Impact
Encapsulation
Hide internal implementation
Prevent unwanted access
Reusability
Share code across projects
DRY principle
Maintainability
Organized code structure
Easier to debug and update
Dependency Management
Explicit dependencies
Clear module relationships
Namespace
Avoid global scope pollution
Prevent naming conflicts
Example: Module patterns
// IIFE Module Pattern (pre-ES6)
const myModule = ( function () {
// Private variables and functions
let privateVar = 'I am private' ;
function privateMethod () {
console. log (privateVar);
}
// Public API
return {
publicMethod : function () {
privateMethod ();
},
publicVar: 'I am public'
};
})();
myModule. publicMethod (); // Works
// myModule.privateVar // undefined (not accessible)
// Revealing Module Pattern
const calculator = ( function () {
// Private
function add ( a , b ) {
return a + b;
}
function subtract ( a , b ) {
return a - b;
}
function multiply ( a , b ) {
return a * b;
}
// Reveal public API
return {
add: add,
subtract: subtract
// multiply is private
};
})();
calculator. add ( 2 , 3 ); // 5
calculator. subtract ( 5 , 2 ); // 3
// calculator.multiply(2, 3) // Error: not exposed
// Namespace Pattern
const MyApp = MyApp || {};
MyApp.Utils = {
formatDate : function ( date ) {
return date. toLocaleDateString ();
},
formatCurrency : function ( amount ) {
return `${ amount . toFixed ( 2 ) }` ;
}
};
MyApp.Models = {
User: class {
constructor ( name ) {
this .name = name;
}
}
};
MyApp.Controllers = {
UserController: class {
createUser ( name ) {
return new MyApp.Models. User (name);
}
}
};
// Usage
const formatted = MyApp.Utils. formatCurrency ( 99.99 );
const user = new MyApp.Models. User ( 'John' );
// ES6 Modules
// math.js
export function add ( a , b ) {
return a + b;
}
export function subtract ( a , b ) {
return a - b;
}
export const PI = 3.14159 ;
// Default export
export default function multiply ( a , b ) {
return a * b;
}
// Import in another file
import multiply, {add, subtract, PI} from './math.js' ;
console. log ( add ( 2 , 3 )); // 5
console. log ( multiply ( 2 , 3 )); // 6
console. log ( PI ); // 3.14159
// Import all as namespace
import * as math from './math.js' ;
console. log (math. add ( 2 , 3 ));
console. log (math. default ( 2 , 3 )); // multiply
// Re-export pattern (index.js)
export {add, subtract} from './math.js' ;
export {User} from './user.js' ;
export { default as multiply} from './math.js' ;
// Lazy loading modules
async function loadModule () {
const module = await import ( './heavy-module.js' );
module . doSomething ();
}
// Module with initialization
// logger.js
class Logger {
constructor () {
this .logs = [];
}
log ( message ) {
this .logs. push ({
message,
timestamp: Date. now ()
});
console. log (message);
}
getLogs () {
return this .logs;
}
}
// Export singleton instance
export default new Logger ();
// Usage
import logger from './logger.js' ;
logger. log ( 'Application started' );
logger. log ( 'User logged in' );
// Dependency injection with modules
// services.js
export class ApiService {
constructor ( baseUrl ) {
this .baseUrl = baseUrl;
}
async get ( endpoint ) {
const response = await fetch ( `${ this . baseUrl }${ endpoint }` );
return response. json ();
}
}
export class AuthService {
constructor ( apiService ) {
this .api = apiService;
}
async login ( credentials ) {
return this .api. get ( '/auth/login' , credentials);
}
}
// app.js
import {ApiService, AuthService} from './services.js' ;
const api = new ApiService ( 'https://api.example.com' );
const auth = new AuthService (api);
await auth. login ({username: 'john' , password: 'pass123' });
Key Points: Module pattern uses IIFE for encapsulation.
Revealing
module pattern explicitly defines public API. ES6 modules with import/export are standard. Use
export default for main export. Namespace objects prevent global pollution. Dynamic imports for
lazy loading. Modules provide encapsulation, reusability, maintainability.
2. Observer Pattern and Event Emitters
Observer Pattern Components
Component
Role
Responsibility
Subject
Observable
Maintains observers list, notifies on change
Observer
Subscriber
Receives notifications, reacts to changes
ConcreteSubject
Specific observable
Stores state, triggers notifications
ConcreteObserver
Specific subscriber
Implements update logic
Event Emitter Methods
Method
Purpose
Usage
on(event, handler)
Register listener
Subscribe to event
off(event, handler)
Remove listener
Unsubscribe from event
emit(event, data)
Trigger event
Notify all listeners
once(event, handler)
One-time listener
Auto-remove after first call
removeAllListeners()
Clear all listeners
Cleanup
Observer Pattern Benefits
Benefit
Description
Use Case
Loose Coupling
Subject and observers independent
Flexible system architecture
Dynamic Relationships
Add/remove observers at runtime
Plugins, extensions
Broadcast Communication
One-to-many notification
State changes, events
Separation of Concerns
Observers handle own logic
Clean code organization
Example: Observer pattern and Event Emitter
// Classic Observer Pattern
class Subject {
constructor () {
this .observers = [];
}
attach ( observer ) {
if ( ! this .observers. includes (observer)) {
this .observers. push (observer);
}
}
detach ( observer ) {
const index = this .observers. indexOf (observer);
if (index > - 1 ) {
this .observers. splice (index, 1 );
}
}
notify ( data ) {
this .observers. forEach ( observer => {
observer. update (data);
});
}
}
class Observer {
update ( data ) {
console. log ( 'Observer received:' , data);
}
}
// Usage
const subject = new Subject ();
const observer1 = new Observer ();
const observer2 = new Observer ();
subject. attach (observer1);
subject. attach (observer2);
subject. notify ({message: 'Hello observers!' });
subject. detach (observer1);
subject. notify ({message: 'Only observer2 gets this' });
// Event Emitter Pattern
class EventEmitter {
constructor () {
this .events = {};
}
on ( event , handler ) {
if ( ! this .events[event]) {
this .events[event] = [];
}
this .events[event]. push (handler);
// Return unsubscribe function
return () => this . off (event, handler);
}
off ( event , handler ) {
if ( ! this .events[event]) return ;
this .events[event] = this .events[event]. filter ( h => h !== handler);
}
emit ( event , data ) {
if ( ! this .events[event]) return ;
this .events[event]. forEach ( handler => {
handler (data);
});
}
once ( event , handler ) {
const onceHandler = ( data ) => {
handler (data);
this . off (event, onceHandler);
};
this . on (event, onceHandler);
}
removeAllListeners ( event ) {
if (event) {
delete this .events[event];
} else {
this .events = {};
}
}
listenerCount ( event ) {
return this .events[event] ? this .events[event]. length : 0 ;
}
}
// Usage
const emitter = new EventEmitter ();
const unsubscribe = emitter. on ( 'data' , ( data ) => {
console. log ( 'Data received:' , data);
});
emitter. emit ( 'data' , {value: 42 });
// One-time listener
emitter. once ( 'complete' , () => {
console. log ( 'Completed!' );
});
emitter. emit ( 'complete' ); // Logs "Completed!"
emitter. emit ( 'complete' ); // Nothing happens
// Unsubscribe
unsubscribe ();
// Real-world example: Store with observers
class Store extends EventEmitter {
constructor ( initialState = {}) {
super ();
this .state = initialState;
}
getState () {
return this .state;
}
setState ( updates ) {
const prevState = { ... this .state};
this .state = { ... this .state, ... updates};
this . emit ( 'change' , {
state: this .state,
prevState: prevState
});
}
subscribe ( handler ) {
return this . on ( 'change' , handler);
}
}
// Usage
const store = new Store ({count: 0 , user: null });
const unsubscribeStore = store. subscribe (({ state , prevState }) => {
console. log ( 'State changed from' , prevState, 'to' , state);
});
store. setState ({count: 1 });
store. setState ({user: {name: 'John' }});
// Real-world: UI Component with events
class Button extends EventEmitter {
constructor ( label ) {
super ();
this .label = label;
this .element = null ;
}
render () {
this .element = document. createElement ( 'button' );
this .element.textContent = this .label;
this .element. addEventListener ( 'click' , () => {
this . emit ( 'click' , {label: this .label});
});
return this .element;
}
setText ( label ) {
this .label = label;
if ( this .element) {
this .element.textContent = label;
}
this . emit ( 'textChange' , {label});
}
}
// Usage
const button = new Button ( 'Click Me' );
button. on ( 'click' , ({ label }) => {
console. log ( `Button "${ label }" clicked` );
});
button. on ( 'textChange' , ({ label }) => {
console. log ( `Button text changed to "${ label }"` );
});
document.body. appendChild (button. render ());
// Async event emitter
class AsyncEventEmitter extends EventEmitter {
async emit ( event , data ) {
if ( ! this .events[event]) return ;
await Promise . all (
this .events[event]. map ( handler => handler (data))
);
}
}
const asyncEmitter = new AsyncEventEmitter ();
asyncEmitter. on ( 'fetch' , async ( url ) => {
const response = await fetch (url);
console. log ( 'Fetched:' , response.status);
});
await asyncEmitter. emit ( 'fetch' , 'https://api.example.com/data' );
// Event namespacing
class NamespacedEventEmitter extends EventEmitter {
emit ( event , data ) {
// Emit specific event
super . emit (event, data);
// Emit namespace events
const parts = event. split ( ':' );
for ( let i = 1 ; i < parts. length ; i ++ ) {
const namespace = parts. slice ( 0 , i). join ( ':' );
super . emit (namespace, data);
}
}
}
const nsEmitter = new NamespacedEventEmitter ();
nsEmitter. on ( 'user' , ( data ) => {
console. log ( 'Any user event:' , data);
});
nsEmitter. on ( 'user:login' , ( data ) => {
console. log ( 'User login:' , data);
});
nsEmitter. emit ( 'user:login' , {id: 123 });
// Triggers both 'user' and 'user:login' handlers
Key Points: Observer pattern enables one-to-many communication.
Subject maintains observers list, notifies on changes. Event emitter with on/off/emit methods.
once() for one-time listeners. Return unsubscribe function from on(). Useful for
state management, UI components, pub/sub systems. Loose coupling between components.
3. Factory Pattern and Builder Pattern
Factory Pattern Types
Type
Description
Use Case
Simple Factory
Single factory function
Basic object creation
Factory Method
Subclasses decide type
Polymorphic creation
Abstract Factory
Family of related objects
Platform-specific objects
Static Factory
Static method creates instances
Named constructors
Builder Pattern Benefits
Benefit
Description
When to Use
Fluent Interface
Chainable method calls
Readable construction code
Immutability
Build once, immutable result
Thread-safe objects
Complex Construction
Step-by-step building
Objects with many parameters
Validation
Validate before creation
Ensure valid state
Factory vs Constructor
Aspect
Factory
Constructor
Return type
Can return any type
Always returns instance
Naming
Descriptive names
Class name only
Caching
Can return cached instances
Always creates new
Polymorphism
Return different subtypes
Fixed type
Example: Factory patterns
// Simple Factory
class User {
constructor ( name , role ) {
this .name = name;
this .role = role;
}
getPermissions () {
return [];
}
}
class Admin extends User {
getPermissions () {
return [ 'read' , 'write' , 'delete' ];
}
}
class Guest extends User {
getPermissions () {
return [ 'read' ];
}
}
// Factory function
function createUser ( name , role ) {
switch (role) {
case 'admin' :
return new Admin (name, role);
case 'guest' :
return new Guest (name, role);
default :
return new User (name, role);
}
}
// Usage
const admin = createUser ( 'John' , 'admin' );
console. log (admin. getPermissions ()); // ['read', 'write', 'delete']
const guest = createUser ( 'Jane' , 'guest' );
console. log (guest. getPermissions ()); // ['read']
// Factory Method Pattern
class Document {
constructor ( title ) {
this .title = title;
}
render () {
throw new Error ( 'Must implement render()' );
}
}
class PDFDocument extends Document {
render () {
return `PDF: ${ this . title }` ;
}
}
class WordDocument extends Document {
render () {
return `Word: ${ this . title }` ;
}
}
class DocumentFactory {
createDocument ( type , title ) {
switch (type) {
case 'pdf' :
return new PDFDocument (title);
case 'word' :
return new WordDocument (title);
default :
throw new Error ( 'Unknown document type' );
}
}
}
// Usage
const factory = new DocumentFactory ();
const pdf = factory. createDocument ( 'pdf' , 'Report' );
console. log (pdf. render ());
// Static Factory Methods
class Color {
constructor ( r , g , b ) {
this .r = r;
this .g = g;
this .b = b;
}
static fromRGB ( r , g , b ) {
return new Color (r, g, b);
}
static fromHex ( hex ) {
const r = parseInt (hex. slice ( 1 , 3 ), 16 );
const g = parseInt (hex. slice ( 3 , 5 ), 16 );
const b = parseInt (hex. slice ( 5 , 7 ), 16 );
return new Color (r, g, b);
}
static red () {
return new Color ( 255 , 0 , 0 );
}
static green () {
return new Color ( 0 , 255 , 0 );
}
static blue () {
return new Color ( 0 , 0 , 255 );
}
}
// Usage
const color1 = Color. fromRGB ( 255 , 128 , 0 );
const color2 = Color. fromHex ( '#FF8000' );
const red = Color. red ();
// Object Pool Factory
class ObjectPool {
constructor ( factory , maxSize = 10 ) {
this .factory = factory;
this .maxSize = maxSize;
this .available = [];
this .inUse = [];
}
acquire () {
let obj;
if ( this .available. length > 0 ) {
obj = this .available. pop ();
} else if ( this .inUse. length < this .maxSize) {
obj = this . factory ();
} else {
throw new Error ( 'Pool exhausted' );
}
this .inUse. push (obj);
return obj;
}
release ( obj ) {
const index = this .inUse. indexOf (obj);
if (index > - 1 ) {
this .inUse. splice (index, 1 );
this .available. push (obj);
}
}
}
// Usage
const connectionPool = new ObjectPool (
() => ({id: Math. random (), connected: true }),
5
);
const conn1 = connectionPool. acquire ();
const conn2 = connectionPool. acquire ();
connectionPool. release (conn1);
// Abstract Factory Pattern
class Button {
render () {
throw new Error ( 'Must implement' );
}
}
class WindowsButton extends Button {
render () {
return '<button class="windows">Click</button>' ;
}
}
class MacButton extends Button {
render () {
return '<button class="mac">Click</button>' ;
}
}
class Checkbox {
render () {
throw new Error ( 'Must implement' );
}
}
class WindowsCheckbox extends Checkbox {
render () {
return '<input type="checkbox" class="windows">' ;
}
}
class MacCheckbox extends Checkbox {
render () {
return '<input type="checkbox" class="mac">' ;
}
}
// Abstract Factory
class UIFactory {
createButton () {
throw new Error ( 'Must implement' );
}
createCheckbox () {
throw new Error ( 'Must implement' );
}
}
class WindowsFactory extends UIFactory {
createButton () {
return new WindowsButton ();
}
createCheckbox () {
return new WindowsCheckbox ();
}
}
class MacFactory extends UIFactory {
createButton () {
return new MacButton ();
}
createCheckbox () {
return new MacCheckbox ();
}
}
// Usage
function createUI ( factory ) {
const button = factory. createButton ();
const checkbox = factory. createCheckbox ();
return {
button: button. render (),
checkbox: checkbox. render ()
};
}
const windowsFactory = new WindowsFactory ();
const windowsUI = createUI (windowsFactory);
const macFactory = new MacFactory ();
const macUI = createUI (macFactory);
Example: Builder pattern
// Builder Pattern
class User2 {
constructor ( builder ) {
this .name = builder.name;
this .email = builder.email;
this .age = builder.age;
this .address = builder.address;
this .phone = builder.phone;
}
}
class UserBuilder {
constructor ( name ) {
this .name = name;
}
withEmail ( email ) {
this .email = email;
return this ; // Return this for chaining
}
withAge ( age ) {
this .age = age;
return this ;
}
withAddress ( address ) {
this .address = address;
return this ;
}
withPhone ( phone ) {
this .phone = phone;
return this ;
}
build () {
// Validation
if ( ! this .email) {
throw new Error ( 'Email is required' );
}
return new User2 ( this );
}
}
// Usage (fluent interface)
const user = new UserBuilder ( 'John Doe' )
. withEmail ( 'john@example.com' )
. withAge ( 30 )
. withAddress ( '123 Main St' )
. withPhone ( '555-1234' )
. build ();
console. log (user);
// Query Builder
class QueryBuilder {
constructor () {
this .query = {
table: null ,
fields: [ '*' ],
conditions: [],
orderBy: null ,
limit: null
};
}
select ( ... fields ) {
this .query.fields = fields;
return this ;
}
from ( table ) {
this .query.table = table;
return this ;
}
where ( condition ) {
this .query.conditions. push (condition);
return this ;
}
orderBy ( field , direction = 'ASC' ) {
this .query.orderBy = {field, direction};
return this ;
}
limit ( count ) {
this .query.limit = count;
return this ;
}
build () {
let sql = `SELECT ${ this . query . fields . join ( ', ' ) }` ;
sql += ` FROM ${ this . query . table }` ;
if ( this .query.conditions. length > 0 ) {
sql += ` WHERE ${ this . query . conditions . join ( ' AND ' ) }` ;
}
if ( this .query.orderBy) {
sql += ` ORDER BY ${ this . query . orderBy . field } ${ this . query . orderBy . direction }` ;
}
if ( this .query.limit) {
sql += ` LIMIT ${ this . query . limit }` ;
}
return sql;
}
}
// Usage
const query = new QueryBuilder ()
. select ( 'id' , 'name' , 'email' )
. from ( 'users' )
. where ( 'age > 18' )
. where ( 'status = "active"' )
. orderBy ( 'name' , 'ASC' )
. limit ( 10 )
. build ();
console. log (query);
// SELECT id, name, email FROM users WHERE age > 18 AND status = "active" ORDER BY name ASC LIMIT 10
// HTML Builder
class HTMLBuilder {
constructor ( tag ) {
this .element = {
tag: tag,
attributes: {},
children: [],
text: null
};
}
attr ( name , value ) {
this .element.attributes[name] = value;
return this ;
}
text ( content ) {
this .element.text = content;
return this ;
}
child ( builder ) {
if (builder instanceof HTMLBuilder ) {
this .element.children. push (builder.element);
} else {
this .element.children. push (builder);
}
return this ;
}
build () {
let html = `<${ this . element . tag }` ;
// Add attributes
for ( const [ key , value ] of Object. entries ( this .element.attributes)) {
html += ` ${ key }="${ value }"` ;
}
html += '>' ;
// Add text or children
if ( this .element.text) {
html += this .element.text;
} else {
this .element.children. forEach ( child => {
if ( typeof child === 'string' ) {
html += child;
} else {
html += new HTMLBuilder (child.tag)
. attr (Object. keys (child.attributes)[ 0 ], Object. values (child.attributes)[ 0 ])
. text (child.text)
. build ();
}
});
}
html += `</${ this . element . tag }>` ;
return html;
}
}
// Usage
const html = new HTMLBuilder ( 'div' )
. attr ( 'class' , 'container' )
. child (
new HTMLBuilder ( 'h1' )
. attr ( 'class' , 'title' )
. text ( 'Hello World' )
)
. child (
new HTMLBuilder ( 'p' )
. text ( 'This is a paragraph' )
)
. build ();
console. log (html);
// <div class="container"><h1 class="title">Hello World</h1><p>This is a paragraph</p></div>
Key Points: Factory pattern encapsulates object creation logic.
Use static factory methods for named constructors. Abstract factory creates families of related objects.
Builder pattern uses fluent interface for complex construction. Builder validates
before creating object. Good for objects with many optional parameters. Both patterns improve code
maintainability.
4. Singleton Pattern and Registry Pattern
Singleton Characteristics
Characteristic
Description
Implementation
Single Instance
Only one instance exists
Private constructor or closure
Global Access
Accessible from anywhere
Static getInstance() method
Lazy Initialization
Created only when needed
Check if instance exists
Thread Safety
Safe in multi-threaded env
Synchronization (JS single-threaded)
Registry Pattern Uses
Use Case
Purpose
Example
Service Locator
Central service registry
DI container
Plugin System
Register/retrieve plugins
Extension manager
Factory Registry
Register factory functions
Component registry
Event Registry
Central event handlers
Event bus
Singleton vs Global Variable
Aspect
Singleton
Global Variable
Instantiation
Lazy, controlled
Immediate
Encapsulation
Private members possible
All public
Inheritance
Can extend classes
Cannot extend
Testability
Can be mocked
Hard to test
Example: Singleton pattern
// Classic Singleton (ES6 class)
class Database {
constructor () {
if (Database.instance) {
return Database.instance;
}
this .connection = null ;
this .connected = false ;
Database.instance = this ;
}
connect ( connectionString ) {
if ( ! this .connected) {
this .connection = connectionString;
this .connected = true ;
console. log ( 'Database connected' );
}
}
query ( sql ) {
if ( ! this .connected) {
throw new Error ( 'Not connected' );
}
console. log ( `Executing: ${ sql }` );
return [];
}
disconnect () {
if ( this .connected) {
this .connection = null ;
this .connected = false ;
console. log ( 'Database disconnected' );
}
}
}
// Usage
const db1 = new Database ();
db1. connect ( 'mongodb://localhost' );
const db2 = new Database ();
console. log (db1 === db2); // true (same instance)
// Module Singleton (Using ES6 modules)
// logger.js
class Logger {
constructor () {
this .logs = [];
}
log ( message ) {
const entry = {
message,
timestamp: new Date (),
level: 'info'
};
this .logs. push (entry);
console. log ( `[${ entry . timestamp . toISOString () }] ${ message }` );
}
error ( message ) {
const entry = {
message,
timestamp: new Date (),
level: 'error'
};
this .logs. push (entry);
console. error ( `[${ entry . timestamp . toISOString () }] ERROR: ${ message }` );
}
getLogs () {
return this .logs;
}
clear () {
this .logs = [];
}
}
// Export singleton instance
export default new Logger ();
// Usage in other files
// import logger from './logger.js';
// logger.log('Application started');
// Singleton with getInstance()
class Config {
static instance = null ;
constructor () {
if (Config.instance) {
throw new Error ( 'Use Config.getInstance()' );
}
this .settings = {};
}
static getInstance () {
if ( ! Config.instance) {
Config.instance = new Config ();
}
return Config.instance;
}
set ( key , value ) {
this .settings[key] = value;
}
get ( key ) {
return this .settings[key];
}
getAll () {
return { ... this .settings};
}
}
// Usage
const config1 = Config. getInstance ();
config1. set ( 'apiUrl' , 'https://api.example.com' );
const config2 = Config. getInstance ();
console. log (config2. get ( 'apiUrl' )); // https://api.example.com
console. log (config1 === config2); // true
// Closure-based Singleton
const AppState = ( function () {
let instance;
function createInstance () {
const state = {
user: null ,
settings: {},
cache: new Map ()
};
return {
getState () {
return state;
},
setUser ( user ) {
state.user = user;
},
getUser () {
return state.user;
},
setSetting ( key , value ) {
state.settings[key] = value;
},
getSetting ( key ) {
return state.settings[key];
}
};
}
return {
getInstance () {
if ( ! instance) {
instance = createInstance ();
}
return instance;
}
};
})();
// Usage
const state1 = AppState. getInstance ();
state1. setUser ({name: 'John' , id: 123 });
const state2 = AppState. getInstance ();
console. log (state2. getUser ()); // {name: 'John', id: 123}
// Singleton with initialization options
class Cache {
static instances = new Map ();
constructor ( name , options = {}) {
const key = name;
if (Cache.instances. has (key)) {
return Cache.instances. get (key);
}
this .name = name;
this .maxSize = options.maxSize || 100 ;
this .ttl = options.ttl || 60000 ; // 1 minute
this .store = new Map ();
Cache.instances. set (key, this );
}
set ( key , value ) {
if ( this .store.size >= this .maxSize) {
const firstKey = this .store. keys (). next ().value;
this .store. delete (firstKey);
}
this .store. set (key, {
value,
expires: Date. now () + this .ttl
});
}
get ( key ) {
const item = this .store. get (key);
if ( ! item) return null ;
if (Date. now () > item.expires) {
this .store. delete (key);
return null ;
}
return item.value;
}
clear () {
this .store. clear ();
}
}
// Usage - named singletons
const userCache = new Cache ( 'users' , {maxSize: 50 });
const productCache = new Cache ( 'products' , {maxSize: 100 });
userCache. set ( 'user:1' , {name: 'John' });
const userCache2 = new Cache ( 'users' );
console. log (userCache2. get ( 'user:1' )); // {name: 'John'}
console. log (userCache === userCache2); // true
Example: Registry pattern
// Service Registry / Dependency Injection Container
class ServiceRegistry {
constructor () {
this .services = new Map ();
this .singletons = new Map ();
}
register ( name , factory , options = {}) {
this .services. set (name, {
factory,
singleton: options.singleton || false ,
dependencies: options.dependencies || []
});
}
resolve ( name ) {
const service = this .services. get (name);
if ( ! service) {
throw new Error ( `Service '${ name }' not registered` );
}
// Return singleton if already created
if (service.singleton && this .singletons. has (name)) {
return this .singletons. get (name);
}
// Resolve dependencies
const dependencies = service.dependencies. map ( dep => this . resolve (dep));
// Create instance
const instance = service. factory ( ... dependencies);
// Store singleton
if (service.singleton) {
this .singletons. set (name, instance);
}
return instance;
}
has ( name ) {
return this .services. has (name);
}
clear () {
this .services. clear ();
this .singletons. clear ();
}
}
// Usage
const registry = new ServiceRegistry ();
// Register services
registry. register ( 'database' , () => {
return {
query : ( sql ) => console. log ( 'Query:' , sql)
};
}, {singleton: true });
registry. register ( 'userService' , ( db ) => {
return {
getUser : ( id ) => {
db. query ( `SELECT * FROM users WHERE id = ${ id }` );
return {id, name: 'John' };
}
};
}, {dependencies: [ 'database' ]});
// Resolve services
const userService = registry. resolve ( 'userService' );
userService. getUser ( 123 );
// Plugin Registry
class PluginRegistry {
constructor () {
this .plugins = new Map ();
this .hooks = new Map ();
}
register ( name , plugin ) {
if ( this .plugins. has (name)) {
throw new Error ( `Plugin '${ name }' already registered` );
}
this .plugins. set (name, plugin);
// Initialize plugin
if ( typeof plugin.init === 'function' ) {
plugin. init ( this );
}
return this ;
}
get ( name ) {
return this .plugins. get (name);
}
has ( name ) {
return this .plugins. has (name);
}
unregister ( name ) {
const plugin = this .plugins. get (name);
if (plugin && typeof plugin.destroy === 'function' ) {
plugin. destroy ();
}
return this .plugins. delete (name);
}
// Hook system
registerHook ( hookName , callback ) {
if ( ! this .hooks. has (hookName)) {
this .hooks. set (hookName, []);
}
this .hooks. get (hookName). push (callback);
}
executeHook ( hookName , data ) {
const callbacks = this .hooks. get (hookName) || [];
let result = data;
for ( const callback of callbacks) {
result = callback (result);
}
return result;
}
}
// Usage
const plugins = new PluginRegistry ();
// Register plugins
plugins. register ( 'markdown' , {
init ( registry ) {
console. log ( 'Markdown plugin initialized' );
registry. registerHook ( 'beforeRender' , ( text ) => {
return text. replace ( / \*\* ( . *? ) \*\* / g , '<strong>$1</strong>' );
});
},
render ( text ) {
return text;
},
destroy () {
console. log ( 'Markdown plugin destroyed' );
}
});
plugins. register ( 'emoji' , {
init ( registry ) {
registry. registerHook ( 'beforeRender' , ( text ) => {
return text. replace ( /:smile:/ g , '😊' );
});
}
});
// Execute hooks
let content = 'Hello **world** :smile:' ;
content = plugins. executeHook ( 'beforeRender' , content);
console. log (content); // Hello <strong>world</strong> 😊
// Component Registry
class ComponentRegistry {
constructor () {
this .components = new Map ();
}
register ( name , component ) {
this .components. set (name, component);
return this ;
}
create ( name , props = {}) {
const Component = this .components. get (name);
if ( ! Component) {
throw new Error ( `Component '${ name }' not registered` );
}
if ( typeof Component === 'function' ) {
return new Component (props);
}
return Component;
}
getAll () {
return Array. from ( this .components. keys ());
}
}
// Usage
const components = new ComponentRegistry ();
components. register ( 'Button' , class {
constructor ( props ) {
this .label = props.label || 'Click' ;
}
render () {
return `<button>${ this . label }</button>` ;
}
});
components. register ( 'Input' , class {
constructor ( props ) {
this .type = props.type || 'text' ;
this .placeholder = props.placeholder || '' ;
}
render () {
return `<input type="${ this . type }" placeholder="${ this . placeholder }">` ;
}
});
const button = components. create ( 'Button' , {label: 'Submit' });
console. log (button. render ());
const input = components. create ( 'Input' , {placeholder: 'Enter name' });
console. log (input. render ());
console. log ( 'Registered components:' , components. getAll ());
Key Points: Singleton pattern ensures single instance with
global access. Use constructor check or getInstance() method. ES6 module exports create natural
singletons. Registry pattern manages collections of objects. Service registry
for dependency injection. Plugin registry for extensible systems. Component registry for dynamic creation.
5. Strategy Pattern and Command Pattern
Strategy Pattern Components
Component
Role
Responsibility
Context
Uses strategy
Maintains reference to strategy
Strategy Interface
Common interface
Defines algorithm method
Concrete Strategy
Implements algorithm
Specific implementation
Command Pattern Elements
Element
Purpose
Implementation
Command
Encapsulates action
execute() method
Receiver
Performs action
Business logic
Invoker
Triggers command
Calls execute()
Client
Creates command
Binds receiver to command
Pattern Benefits
Pattern
Key Benefit
Use Case
Strategy
Runtime algorithm selection
Sorting, validation, payment methods
Command
Undo/redo, queuing, logging
Text editors, transactions, macros
Example: Strategy pattern
// Strategy Pattern - Sorting algorithms
class SortStrategy {
sort ( array ) {
throw new Error ( 'Must implement sort()' );
}
}
class BubbleSortStrategy extends SortStrategy {
sort ( array ) {
const arr = [ ... array];
for ( let i = 0 ; i < arr. length ; i ++ ) {
for ( let j = 0 ; j < arr. length - 1 - i; j ++ ) {
if (arr[j] > arr[j + 1 ]) {
[arr[j], arr[j + 1 ]] = [arr[j + 1 ], arr[j]];
}
}
}
return arr;
}
}
class QuickSortStrategy extends SortStrategy {
sort ( array ) {
if (array. length <= 1 ) return array;
const pivot = array[Math. floor (array. length / 2 )];
const left = array. filter ( x => x < pivot);
const middle = array. filter ( x => x === pivot);
const right = array. filter ( x => x > pivot);
return [ ... this . sort (left), ... middle, ... this . sort (right)];
}
}
class Sorter {
constructor ( strategy ) {
this .strategy = strategy;
}
setStrategy ( strategy ) {
this .strategy = strategy;
}
sort ( array ) {
console. log ( `Sorting with ${ this . strategy . constructor . name }` );
return this .strategy. sort (array);
}
}
// Usage
const data = [ 5 , 2 , 8 , 1 , 9 ];
const sorter = new Sorter ( new BubbleSortStrategy ());
console. log (sorter. sort (data));
sorter. setStrategy ( new QuickSortStrategy ());
console. log (sorter. sort (data));
// Strategy Pattern - Validation
class ValidationStrategy {
validate ( value ) {
throw new Error ( 'Must implement validate()' );
}
}
class EmailValidation extends ValidationStrategy {
validate ( value ) {
const regex = / ^ [ ^ \s@] + @ [ ^ \s@] + \. [ ^ \s@] +$ / ;
return regex. test (value);
}
}
class PhoneValidation extends ValidationStrategy {
validate ( value ) {
const regex = / ^ \d {3} - \d {3} - \d {4}$ / ;
return regex. test (value);
}
}
class PasswordValidation extends ValidationStrategy {
validate ( value ) {
return value. length >= 8 &&
/ [A-Z] / . test (value) &&
/ [a-z] / . test (value) &&
/ [0-9] / . test (value);
}
}
class Validator {
constructor () {
this .strategies = new Map ();
}
addStrategy ( field , strategy ) {
this .strategies. set (field, strategy);
}
validate ( data ) {
const errors = {};
for ( const [ field , strategy ] of this .strategies) {
const value = data[field];
if ( ! strategy. validate (value)) {
errors[field] = `Invalid ${ field }` ;
}
}
return {
valid: Object. keys (errors). length === 0 ,
errors
};
}
}
// Usage
const validator = new Validator ();
validator. addStrategy ( 'email' , new EmailValidation ());
validator. addStrategy ( 'phone' , new PhoneValidation ());
validator. addStrategy ( 'password' , new PasswordValidation ());
const result = validator. validate ({
email: 'user@example.com' ,
phone: '555-123-4567' ,
password: 'Pass123'
});
console. log (result); // {valid: true, errors: {}}
// Strategy Pattern - Payment methods
class PaymentStrategy {
pay ( amount ) {
throw new Error ( 'Must implement pay()' );
}
}
class CreditCardPayment extends PaymentStrategy {
constructor ( cardNumber ) {
super ();
this .cardNumber = cardNumber;
}
pay ( amount ) {
console. log ( `Paid ${ amount } with credit card ${ this . cardNumber }` );
return {success: true , transactionId: Math. random ()};
}
}
class PayPalPayment extends PaymentStrategy {
constructor ( email ) {
super ();
this .email = email;
}
pay ( amount ) {
console. log ( `Paid ${ amount } via PayPal (${ this . email })` );
return {success: true , transactionId: Math. random ()};
}
}
class CryptoPayment extends PaymentStrategy {
constructor ( wallet ) {
super ();
this .wallet = wallet;
}
pay ( amount ) {
console. log ( `Paid ${ amount } with crypto (${ this . wallet })` );
return {success: true , transactionId: Math. random ()};
}
}
class ShoppingCart {
constructor () {
this .items = [];
this .paymentStrategy = null ;
}
addItem ( item ) {
this .items. push (item);
}
setPaymentStrategy ( strategy ) {
this .paymentStrategy = strategy;
}
checkout () {
const total = this .items. reduce (( sum , item ) => sum + item.price, 0 );
if ( ! this .paymentStrategy) {
throw new Error ( 'Payment method not set' );
}
return this .paymentStrategy. pay (total);
}
}
// Usage
const cart = new ShoppingCart ();
cart. addItem ({name: 'Book' , price: 20 });
cart. addItem ({name: 'Pen' , price: 5 });
cart. setPaymentStrategy ( new CreditCardPayment ( '1234-5678-9012-3456' ));
cart. checkout ();
cart. setPaymentStrategy ( new PayPalPayment ( 'user@example.com' ));
cart. checkout ();
Example: Command pattern
// Command Pattern - Text editor
class Command {
execute () {
throw new Error ( 'Must implement execute()' );
}
undo () {
throw new Error ( 'Must implement undo()' );
}
}
class InsertTextCommand extends Command {
constructor ( receiver , text , position ) {
super ();
this .receiver = receiver;
this .text = text;
this .position = position;
}
execute () {
this .receiver. insertText ( this .text, this .position);
}
undo () {
this .receiver. deleteText ( this .position, this .text. length );
}
}
class DeleteTextCommand extends Command {
constructor ( receiver , position , length ) {
super ();
this .receiver = receiver;
this .position = position;
this . length = length;
this .deletedText = null ;
}
execute () {
this .deletedText = this .receiver. deleteText ( this .position, this . length );
}
undo () {
this .receiver. insertText ( this .deletedText, this .position);
}
}
// Receiver
class TextEditor {
constructor () {
this .content = '' ;
}
insertText ( text , position ) {
this .content =
this .content. slice ( 0 , position) +
text +
this .content. slice (position);
console. log ( 'Content:' , this .content);
}
deleteText ( position , length ) {
const deleted = this .content. slice (position, position + length);
this .content =
this .content. slice ( 0 , position) +
this .content. slice (position + length);
console. log ( 'Content:' , this .content);
return deleted;
}
getText () {
return this .content;
}
}
// Invoker
class CommandManager {
constructor () {
this .history = [];
this .currentIndex = - 1 ;
}
execute ( command ) {
// Remove any commands after current index
this .history = this .history. slice ( 0 , this .currentIndex + 1 );
command. execute ();
this .history. push (command);
this .currentIndex ++ ;
}
undo () {
if ( this .currentIndex < 0 ) {
console. log ( 'Nothing to undo' );
return ;
}
const command = this .history[ this .currentIndex];
command. undo ();
this .currentIndex -- ;
}
redo () {
if ( this .currentIndex >= this .history. length - 1 ) {
console. log ( 'Nothing to redo' );
return ;
}
this .currentIndex ++ ;
const command = this .history[ this .currentIndex];
command. execute ();
}
}
// Usage
const editor = new TextEditor ();
const manager = new CommandManager ();
manager. execute ( new InsertTextCommand (editor, 'Hello' , 0 ));
manager. execute ( new InsertTextCommand (editor, ' World' , 5 ));
manager. execute ( new DeleteTextCommand (editor, 5 , 6 ));
manager. undo (); // Restore " World"
manager. undo (); // Remove " World"
manager. redo (); // Add " World" again
// Command Pattern - Remote control
class Light {
constructor ( location ) {
this .location = location;
this .isOn = false ;
}
on () {
this .isOn = true ;
console. log ( `${ this . location } light is ON` );
}
off () {
this .isOn = false ;
console. log ( `${ this . location } light is OFF` );
}
}
class LightOnCommand extends Command {
constructor ( light ) {
super ();
this .light = light;
}
execute () {
this .light. on ();
}
undo () {
this .light. off ();
}
}
class LightOffCommand extends Command {
constructor ( light ) {
super ();
this .light = light;
}
execute () {
this .light. off ();
}
undo () {
this .light. on ();
}
}
class RemoteControl {
constructor () {
this .commands = {};
this .history = [];
}
setCommand ( button , command ) {
this .commands[button] = command;
}
pressButton ( button ) {
const command = this .commands[button];
if (command) {
command. execute ();
this .history. push (command);
}
}
pressUndo () {
const command = this .history. pop ();
if (command) {
command. undo ();
}
}
}
// Usage
const livingRoomLight = new Light ( 'Living Room' );
const bedroomLight = new Light ( 'Bedroom' );
const remote = new RemoteControl ();
remote. setCommand ( 'button1' , new LightOnCommand (livingRoomLight));
remote. setCommand ( 'button2' , new LightOffCommand (livingRoomLight));
remote. setCommand ( 'button3' , new LightOnCommand (bedroomLight));
remote. pressButton ( 'button1' ); // Living Room light ON
remote. pressButton ( 'button3' ); // Bedroom light ON
remote. pressUndo (); // Bedroom light OFF
// Macro Command - multiple commands
class MacroCommand extends Command {
constructor ( commands ) {
super ();
this .commands = commands;
}
execute () {
this .commands. forEach ( cmd => cmd. execute ());
}
undo () {
// Undo in reverse order
for ( let i = this .commands. length - 1 ; i >= 0 ; i -- ) {
this .commands[i]. undo ();
}
}
}
// Usage
const partyMode = new MacroCommand ([
new LightOnCommand (livingRoomLight),
new LightOnCommand (bedroomLight)
]);
remote. setCommand ( 'party' , partyMode);
remote. pressButton ( 'party' ); // Turn on all lights
Key Points: Strategy pattern encapsulates algorithms as
interchangeable objects. Set strategy at runtime. Use for sorting, validation, payment methods.
Command pattern encapsulates actions as objects. Enables undo/redo with
command history. Macro commands combine multiple commands. Both patterns promote loose coupling.
6. Decorator Pattern and Proxy Pattern
Decorator Pattern Features
Feature
Description
Benefit
Wrapping
Wrap object to add behavior
Extend without modifying
Composition
Stack multiple decorators
Flexible combinations
Transparent
Same interface as original
Drop-in replacement
Runtime
Add behavior dynamically
Configuration flexibility
Proxy Pattern Types
Type
Purpose
Use Case
Virtual Proxy
Lazy initialization
Expensive object creation
Protection Proxy
Access control
Authentication, authorization
Remote Proxy
Remote object access
API calls, RPC
Caching Proxy
Cache results
Performance optimization
Logging Proxy
Log method calls
Debugging, auditing
ES6 Proxy Traps
Trap
Intercepts
Usage
get
Property access
obj.prop
set
Property assignment
obj.prop = value
has
in operator
'prop' in obj
deleteProperty
delete operator
delete obj.prop
apply
Function call
func(...args)
construct
new operator
new Constructor()
Example: Decorator pattern
// Decorator Pattern - Coffee shop
class Coffee {
cost () {
return 5 ;
}
description () {
return 'Coffee' ;
}
}
class CoffeeDecorator {
constructor ( coffee ) {
this .coffee = coffee;
}
cost () {
return this .coffee. cost ();
}
description () {
return this .coffee. description ();
}
}
class MilkDecorator extends CoffeeDecorator {
cost () {
return this .coffee. cost () + 1 ;
}
description () {
return this .coffee. description () + ', Milk' ;
}
}
class SugarDecorator extends CoffeeDecorator {
cost () {
return this .coffee. cost () + 0.5 ;
}
description () {
return this .coffee. description () + ', Sugar' ;
}
}
class WhipDecorator extends CoffeeDecorator {
cost () {
return this .coffee. cost () + 1.5 ;
}
description () {
return this .coffee. description () + ', Whip' ;
}
}
// Usage
let myCoffee = new Coffee ();
console. log ( `${ myCoffee . description () }: ${ myCoffee . cost () }` );
myCoffee = new MilkDecorator (myCoffee);
console. log ( `${ myCoffee . description () }: ${ myCoffee . cost () }` );
myCoffee = new SugarDecorator (myCoffee);
myCoffee = new WhipDecorator (myCoffee);
console. log ( `${ myCoffee . description () }: ${ myCoffee . cost () }` );
// Coffee, Milk, Sugar, Whip: $8
// Function Decorator
function logger ( func ) {
return function ( ... args ) {
console. log ( `Calling ${ func . name } with` , args);
const result = func ( ... args);
console. log ( `Result:` , result);
return result;
};
}
function memoize ( func ) {
const cache = new Map ();
return function ( ... args ) {
const key = JSON . stringify (args);
if (cache. has (key)) {
console. log ( 'Cache hit' );
return cache. get (key);
}
const result = func ( ... args);
cache. set (key, result);
return result;
};
}
function timer ( func ) {
return function ( ... args ) {
const start = performance. now ();
const result = func ( ... args);
const end = performance. now ();
console. log ( `${ func . name } took ${ ( end - start ). toFixed ( 2 ) }ms` );
return result;
};
}
// Usage
function fibonacci ( n ) {
if (n <= 1 ) return n;
return fibonacci (n - 1 ) + fibonacci (n - 2 );
}
const decoratedFib = timer ( memoize ( logger (fibonacci)));
decoratedFib ( 10 );
// Class Method Decorator (using decorator proposal)
function readonly ( target , key , descriptor ) {
descriptor.writable = false ;
return descriptor;
}
function validate ( target , key , descriptor ) {
const original = descriptor.value;
descriptor. value = function ( ... args ) {
if (args. some ( arg => typeof arg !== 'number' )) {
throw new Error ( 'All arguments must be numbers' );
}
return original. apply ( this , args);
};
return descriptor;
}
class Calculator {
@readonly
PI = 3.14159 ;
@validate
add ( a , b ) {
return a + b;
}
}
// Without decorator syntax (current JavaScript)
class Calculator2 {
constructor () {
Object. defineProperty ( this , 'PI' , {
value: 3.14159 ,
writable: false
});
}
}
// Wrap with validation
Calculator2 . prototype . add = function ( a , b ) {
if ( typeof a !== 'number' || typeof b !== 'number' ) {
throw new Error ( 'All arguments must be numbers' );
}
return a + b;
};
Example: Proxy pattern
// Virtual Proxy - Lazy loading
class Image {
constructor ( filename ) {
this .filename = filename;
this . loadFromDisk ();
}
loadFromDisk () {
console. log ( `Loading ${ this . filename } from disk` );
}
display () {
console. log ( `Displaying ${ this . filename }` );
}
}
class ImageProxy {
constructor ( filename ) {
this .filename = filename;
this .image = null ;
}
display () {
if ( ! this .image) {
this .image = new Image ( this .filename);
}
this .image. display ();
}
}
// Usage
const proxy = new ImageProxy ( 'photo.jpg' );
// Image not loaded yet
proxy. display (); // Loads and displays
proxy. display (); // Just displays (already loaded)
// Protection Proxy - Access control
class BankAccount {
constructor ( balance ) {
this .balance = balance;
}
deposit ( amount ) {
this .balance += amount;
console. log ( `Deposited ${ amount }, balance: ${ this . balance }` );
}
withdraw ( amount ) {
if (amount > this .balance) {
throw new Error ( 'Insufficient funds' );
}
this .balance -= amount;
console. log ( `Withdrew ${ amount }, balance: ${ this . balance }` );
}
getBalance () {
return this .balance;
}
}
class BankAccountProxy {
constructor ( account , password ) {
this .account = account;
this .password = password;
}
authenticate ( password ) {
return password === this .password;
}
deposit ( amount , password ) {
if ( ! this . authenticate (password)) {
throw new Error ( 'Authentication failed' );
}
this .account. deposit (amount);
}
withdraw ( amount , password ) {
if ( ! this . authenticate (password)) {
throw new Error ( 'Authentication failed' );
}
this .account. withdraw (amount);
}
getBalance ( password ) {
if ( ! this . authenticate (password)) {
throw new Error ( 'Authentication failed' );
}
return this .account. getBalance ();
}
}
// Usage
const account = new BankAccount ( 1000 );
const secureAccount = new BankAccountProxy (account, 'secret123' );
secureAccount. deposit ( 500 , 'secret123' );
secureAccount. withdraw ( 200 , 'secret123' );
// secureAccount.withdraw(200, 'wrong'); // Error
// ES6 Proxy - Validation
const user = {
name: 'John' ,
age: 30 ,
email: 'john@example.com'
};
const validatedUser = new Proxy (user, {
set ( target , property , value ) {
if (property === 'age' ) {
if ( typeof value !== 'number' || value < 0 ) {
throw new Error ( 'Age must be a positive number' );
}
}
if (property === 'email' ) {
if ( ! / ^ [ ^ \s@] + @ [ ^ \s@] + \. [ ^ \s@] +$ / . test (value)) {
throw new Error ( 'Invalid email format' );
}
}
target[property] = value;
return true ;
}
});
validatedUser.age = 35 ; // OK
// validatedUser.age = -5; // Error
// validatedUser.email = 'invalid'; // Error
// ES6 Proxy - Logging
function createLoggingProxy ( obj , name ) {
return new Proxy (obj, {
get ( target , property ) {
console. log ( `[GET] ${ name }.${ property }` );
return target[property];
},
set ( target , property , value ) {
console. log ( `[SET] ${ name }.${ property } = ${ value }` );
target[property] = value;
return true ;
}
});
}
const person = createLoggingProxy ({
name: 'John' ,
age: 30
}, 'person' );
person.name; // [GET] person.name
person.age = 31 ; // [SET] person.age = 31
// ES6 Proxy - Negative array indexes
function createArrayProxy ( arr ) {
return new Proxy (arr, {
get ( target , property ) {
const index = parseInt (property);
if ( ! isNaN (index) && index < 0 ) {
return target[target. length + index];
}
return target[property];
}
});
}
const arr = createArrayProxy ([ 1 , 2 , 3 , 4 , 5 ]);
console. log (arr[ - 1 ]); // 5
console. log (arr[ - 2 ]); // 4
// ES6 Proxy - Observable
function createObservable ( obj , onChange ) {
return new Proxy (obj, {
set ( target , property , value ) {
const oldValue = target[property];
target[property] = value;
onChange ({
property,
oldValue,
newValue: value
});
return true ;
}
});
}
const state = createObservable ({count: 0 }, ( change ) => {
console. log ( `${ change . property } changed from ${ change . oldValue } to ${ change . newValue }` );
});
state.count = 1 ; // count changed from 0 to 1
state.count = 2 ; // count changed from 1 to 2
// Caching Proxy
function createCachingProxy ( target ) {
const cache = new Map ();
return new Proxy (target, {
apply ( target , thisArg , args ) {
const key = JSON . stringify (args);
if (cache. has (key)) {
console. log ( 'Cache hit' );
return cache. get (key);
}
const result = target. apply (thisArg, args);
cache. set (key, result);
return result;
}
});
}
function expensiveOperation ( n ) {
console. log ( 'Computing...' );
let result = 0 ;
for ( let i = 0 ; i < n * 1000000 ; i ++ ) {
result += i;
}
return result;
}
const cachedOp = createCachingProxy (expensiveOperation);
cachedOp ( 10 ); // Computing...
cachedOp ( 10 ); // Cache hit (instant)
cachedOp ( 20 ); // Computing...
Key Points: Decorator pattern wraps objects to add behavior
dynamically. Stack multiple decorators for flexible combinations. Proxy pattern
controls access to objects. Virtual proxy for lazy loading. Protection proxy for access control. ES6 Proxy
with traps (get, set, apply) enables powerful interceptors. Use for
validation, logging, caching, observables.
Section 22 Summary: Modern JavaScript Patterns and Techniques
Module Patterns: IIFE modules, revealing module, ES6 modules (import/export), namespace
objects for organization
Observer Pattern: Subject maintains observers, notify on changes, Event Emitter with
on/off/emit methods
Factory Pattern: Encapsulate object creation, static factory methods, abstract factory
for related objects
Builder Pattern: Fluent interface for complex construction, chainable methods, validation
before creation
Singleton: Single instance with global access, getInstance() method, ES6 module exports
as natural singletons
Registry Pattern: Manage collections of objects, service registry (DI), plugin registry,
component registry
Strategy Pattern: Encapsulate algorithms, runtime selection, interchangeable strategies
for sorting/validation/payments
Command Pattern: Encapsulate actions as objects, undo/redo with command history, macro
commands for multiple actions
Decorator Pattern: Wrap objects to add behavior, stack decorators, same interface as
original object
Proxy Pattern: Control object access, types (virtual, protection, caching), ES6 Proxy
traps (get/set/apply)
Pattern Benefits: All patterns promote loose coupling, code reusability, maintainability,
testability
Use Cases: Choose pattern based on problem: Observer for events, Factory for creation,
Strategy for algorithms, Command for undo/redo, Proxy for access control