Patterns & Best Practices
Common Patterns
Debouncing
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// Usage
const debouncedSearch = debounce(query => {
console.log('Searching for:', query);
// Perform search
}, 300);
// Advanced debounce with immediate option
function advancedDebounce(func, delay, immediate = false) {
let timeoutId;
return function (...args) {
const callNow = immediate && !timeoutId;
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
timeoutId = null;
if (!immediate) func.apply(this, args);
}, delay);
if (callNow) func.apply(this, args);
};
}
Throttling
function throttle(func, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
// Usage
const throttledHandler = throttle(() => {
console.log('Scroll event');
}, 100);
window.addEventListener('scroll', throttledHandler);
// Advanced throttle with trailing option
function advancedThrottle(func, limit, options = {}) {
let timeout;
let previous = 0;
let result;
const later = function (context, args) {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(context, args);
};
const throttled = function (...args) {
const now = Date.now();
if (!previous && options.leading === false) previous = now;
const remaining = limit - (now - previous);
if (remaining <= 0 || remaining > limit) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(this, args);
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(() => later(this, args), remaining);
}
return result;
};
throttled.cancel = function () {
clearTimeout(timeout);
previous = 0;
timeout = null;
};
return throttled;
}
Memoization
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.apply(this, args);
cache.set(key, result);
return result;
};
}
// Usage
const memoizedFib = memoize(function fibonacci(n) {
if (n < 2) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
// Advanced memoization with TTL
function memoizeWithTTL(fn, ttl = 60000) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.value;
}
const result = fn.apply(this, args);
cache.set(key, {
value: result,
timestamp: Date.now(),
});
return result;
};
}
// LRU Cache implementation
class LRUCache {
constructor(maxSize = 100) {
this.maxSize = maxSize;
this.cache = new Map();
}
get(key) {
if (this.cache.has(key)) {
const value = this.cache.get(key);
// Move to end (most recently used)
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
return undefined;
}
set(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.maxSize) {
// Remove least recently used (first item)
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
}
Observer Pattern
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
// Return unsubscribe function
return () => this.off(event, callback);
}
off(event, callback) {
if (!this.events[event]) return;
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
emit(event, ...args) {
if (!this.events[event]) return;
this.events[event].forEach(callback => {
try {
callback(...args);
} catch (error) {
console.error('Error in event callback:', error);
}
});
}
once(event, callback) {
const onceCallback = (...args) => {
callback(...args);
this.off(event, onceCallback);
};
this.on(event, onceCallback);
}
}
// Usage
const emitter = new EventEmitter();
const unsubscribe = emitter.on('user:login', user => {
console.log('User logged in:', user.name);
});
emitter.emit('user:login', { name: 'John' });
unsubscribe(); // Remove listener
Singleton Pattern
// ES6 Class Singleton
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
this.data = [];
Singleton.instance = this;
}
addData(item) {
this.data.push(item);
}
getData() {
return this.data;
}
}
// Module Singleton
const singleton = (() => {
let instance;
function createInstance() {
return {
data: [],
addData(item) {
this.data.push(item);
},
getData() {
return this.data;
},
};
}
return {
getInstance() {
if (!instance) {
instance = createInstance();
}
return instance;
},
};
})();
// Modern Singleton with Symbol
const SingletonSymbol = Symbol('Singleton');
class ModernSingleton {
constructor() {
if (ModernSingleton[SingletonSymbol]) {
return ModernSingleton[SingletonSymbol];
}
this.data = new Map();
ModernSingleton[SingletonSymbol] = this;
}
set(key, value) {
this.data.set(key, value);
}
get(key) {
return this.data.get(key);
}
}
Factory Pattern
// Simple Factory
class UserFactory {
static createUser(type, data) {
switch (type) {
case 'admin':
return new AdminUser(data);
case 'moderator':
return new ModeratorUser(data);
case 'regular':
return new RegularUser(data);
default:
throw new Error('Unknown user type');
}
}
}
class AdminUser {
constructor({ name, email }) {
this.name = name;
this.email = email;
this.permissions = ['read', 'write', 'delete', 'admin'];
}
}
class ModeratorUser {
constructor({ name, email }) {
this.name = name;
this.email = email;
this.permissions = ['read', 'write', 'moderate'];
}
}
class RegularUser {
constructor({ name, email }) {
this.name = name;
this.email = email;
this.permissions = ['read'];
}
}
// Abstract Factory
class DatabaseFactory {
static create(type) {
const factories = {
mysql: MySQLFactory,
postgres: PostgreSQLFactory,
mongodb: MongoDBFactory,
};
const Factory = factories[type];
if (!Factory) {
throw new Error('Unknown database type');
}
return new Factory();
}
}
class MySQLFactory {
createConnection() {
return new MySQLConnection();
}
createQuery() {
return new MySQLQuery();
}
}
Best Practices
Code Organization
// Use strict mode
'use strict';
// Constants at top
const API_URL = 'https://api.example.com';
const MAX_RETRIES = 3;
const CACHE_TTL = 60000;
// Group related functionality
class UserService {
constructor(apiClient) {
this.apiClient = apiClient;
this.cache = new Map();
}
async fetchUser(id) {
// Check cache first
if (this.cache.has(id)) {
return this.cache.get(id);
}
try {
const user = await this.apiClient.get(`/users/${id}`);
this.cache.set(id, user);
return user;
} catch (error) {
console.error('Failed to fetch user:', error);
throw error;
}
}
async updateUser(id, data) {
try {
const updatedUser = await this.apiClient.put(`/users/${id}`, data);
this.cache.set(id, updatedUser);
return updatedUser;
} catch (error) {
console.error('Failed to update user:', error);
throw error;
}
}
}
Error Prevention
// Type checking
function processNumber(value) {
if (typeof value !== 'number' || isNaN(value)) {
throw new TypeError('Expected a valid number');
}
return value * 2;
}
// Input validation
function createUser(userData) {
const schema = {
name: 'string',
email: 'string',
age: 'number',
};
// Validate required fields
for (const [field, type] of Object.entries(schema)) {
if (!(field in userData)) {
throw new Error(`Missing required field: ${field}`);
}
if (typeof userData[field] !== type) {
throw new TypeError(`Field ${field} must be of type ${type}`);
}
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(userData.email)) {
throw new Error('Invalid email format');
}
// Validate age range
if (userData.age < 0 || userData.age > 150) {
throw new Error('Age must be between 0 and 150');
}
return {
id: generateId(),
...userData,
createdAt: new Date(),
};
}
// Safe object access
function safeGet(obj, path, defaultValue = undefined) {
const keys = path.split('.');
let current = obj;
for (const key of keys) {
if (current == null || typeof current !== 'object') {
return defaultValue;
}
current = current[key];
}
return current !== undefined ? current : defaultValue;
}
// Usage
const user = { profile: { settings: { theme: 'dark' } } };
const theme = safeGet(user, 'profile.settings.theme', 'light');
Performance Tips
// Avoid global variables
(function () {
// Code here is in local scope
const localVar = 'safe';
})();
// Use const/let instead of var
const items = [];
let currentIndex = 0;
// Efficient array operations
const activeItems = items.filter(item => item.active);
const processedItems = activeItems.map(item => processItem(item));
// Prefer for...of for arrays
for (const item of items) {
console.log(item);
}
// Use for...in for objects
for (const key in object) {
if (object.hasOwnProperty(key)) {
console.log(key, object[key]);
}
}
// Use Map for object-like structures with non-string keys
const cache = new Map();
cache.set(1, 'value');
cache.set('key', 'another value');
// Efficient string concatenation
const parts = ['Hello', ' ', 'World'];
const result = parts.join(''); // Better than multiple +
// Use template literals for complex strings
const html = `
<div class="${className}">
<h1>${title}</h1>
<p>${description}</p>
</div>
`;
// Minimize DOM queries
const container = document.querySelector('.container');
const items = container.querySelectorAll('.item');
// Batch DOM updates
const fragment = document.createDocumentFragment();
items.forEach(item => {
const clone = item.cloneNode(true);
fragment.appendChild(clone);
});
container.appendChild(fragment);
Memory Management
// Remove event listeners
class Component {
constructor(element) {
this.element = element;
this.handleClick = this.handleClick.bind(this);
this.element.addEventListener('click', this.handleClick);
}
destroy() {
this.element.removeEventListener('click', this.handleClick);
this.element = null;
}
handleClick(event) {
console.log('Clicked');
}
}
// Clear timers
class Timer {
constructor() {
this.intervals = new Set();
this.timeouts = new Set();
}
setInterval(callback, delay) {
const id = setInterval(callback, delay);
this.intervals.add(id);
return id;
}
setTimeout(callback, delay) {
const id = setTimeout(() => {
callback();
this.timeouts.delete(id);
}, delay);
this.timeouts.add(id);
return id;
}
clearAll() {
this.intervals.forEach(id => clearInterval(id));
this.timeouts.forEach(id => clearTimeout(id));
this.intervals.clear();
this.timeouts.clear();
}
}
// WeakMap for private data
const privateData = new WeakMap();
class User {
constructor(name, email) {
privateData.set(this, {
email: email,
id: generateId(),
});
this.name = name;
}
getEmail() {
return privateData.get(this).email;
}
getId() {
return privateData.get(this).id;
}
}
Security Best Practices
// Sanitize user input
function sanitizeInput(input) {
if (typeof input !== 'string') {
throw new TypeError('Input must be a string');
}
return input
.replace(/[<>"'&]/g, char => {
const entities = {
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'&': '&',
};
return entities[char];
})
.trim();
}
// Validate URLs
function isValidURL(string) {
try {
const url = new URL(string);
return ['http:', 'https:'].includes(url.protocol);
} catch {
return false;
}
}
// Safe JSON parsing
function safeJsonParse(jsonString, defaultValue = null) {
try {
return JSON.parse(jsonString);
} catch (error) {
console.warn('Invalid JSON:', error.message);
return defaultValue;
}
}
// Content Security Policy helpers
function createSecureElement(tagName, attributes = {}) {
const element = document.createElement(tagName);
// Whitelist allowed attributes
const allowedAttributes = {
img: ['src', 'alt', 'width', 'height'],
a: ['href', 'title', 'target'],
div: ['class', 'id'],
span: ['class', 'id'],
};
const allowed = allowedAttributes[tagName] || [];
for (const [key, value] of Object.entries(attributes)) {
if (allowed.includes(key)) {
element.setAttribute(key, value);
}
}
return element;
}