Chapter 15: Performance and Best Practices
Memory Management and Performance
// Avoid memory leaks class EventManager { constructor() { this.listeners = new Map(); } addEventListener(element, event, handler) { // Store references for cleanup if (!this.listeners.has(element)) { this.listeners.set(element, new Map()); } this.listeners.get(element).set(event, handler); element.addEventListener(event, handler); } removeEventListener(element, event) { const elementListeners = this.listeners.get(element); if (elementListeners) { const handler = elementListeners.get(event); if (handler) { element.removeEventListener(event, handler); elementListeners.delete(event); } } } cleanup() { // Clean up all listeners for (const [element, events] of this.listeners) { for (const [event, handler] of events) { element.removeEventListener(event, handler); } } this.listeners.clear(); } } // Debouncing and throttling function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => func.apply(this, args), delay); }; } function throttle(func, limit) { let inThrottle; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // Usage examples const debouncedSearch = debounce((query) => { console.log(`Searching for: ${query}`); }, 300); const throttledScroll = throttle(() => { console.log('Scroll event handled'); }, 100); // Efficient array operations // ❌ Inefficient - creates new arrays let numbers = [1, 2, 3, 4, 5]; numbers = numbers.filter(n => n > 2).map(n => n * 2).filter(n => n < 10); // ✅ More efficient - single pass numbers = [1, 2, 3, 4, 5].reduce((acc, n) => { if (n > 2) { const doubled = n * 2; if (doubled < 10) { acc.push(doubled); } } return acc; }, []);
Error Handling Best Practices
// Custom error classes class ValidationError extends Error { constructor(message, field) { super(message); this.name = 'ValidationError'; this.field = field; } } class NetworkError extends Error { constructor(message, statusCode) { super(message); this.name = 'NetworkError'; this.statusCode = statusCode; } } // Comprehensive error handling async function processUserData(userData) { try { // Validation if (!userData.email) { throw new ValidationError('Email is required', 'email'); } if (!userData.email.includes('@')) { throw new ValidationError('Invalid email format', 'email'); } // API call with timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); const response = await fetch('/api/users', { method: 'POST', body: JSON.stringify(userData), headers: { 'Content-Type': 'application/json' }, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new NetworkError( `HTTP ${response.status}: ${response.statusText}`, response.status ); } return await response.json(); } catch (error) { // Handle different error types if (error instanceof ValidationError) { console.error(`Validation error in ${error.field}: ${error.message}`); // Show user-friendly message } else if (error instanceof NetworkError) { console.error(`Network error (${error.statusCode}): ${error.message}`); // Retry logic or fallback } else if (error.name === 'AbortError') { console.error('Request timed out'); // Handle timeout } else { console.error('Unexpected error:', error); // Generic error handling } throw error; // Re-throw if calling code needs to handle it } } // Global error handling window.addEventListener('error', (event) => { console.error('Global error:', event.error); // Send to error reporting service }); window.addEventListener('unhandledrejection', (event) => { console.error('Unhandled promise rejection:', event.reason); event.preventDefault(); // Prevent default browser behavior });
Performance Optimization Techniques
DOM Manipulation Optimization
// ❌ Inefficient - multiple DOM operations function inefficientDOMUpdate(items) { const container = document.getElementById('container'); // Each iteration triggers reflow/repaint for (const item of items) { const div = document.createElement('div'); div.textContent = item.name; container.appendChild(div); // Expensive! } } // ✅ Efficient - batch DOM operations function efficientDOMUpdate(items) { const container = document.getElementById('container'); const fragment = document.createDocumentFragment(); // Build in memory first for (const item of items) { const div = document.createElement('div'); div.textContent = item.name; fragment.appendChild(div); } // Single DOM operation container.appendChild(fragment); } // ✅ Even better - use innerHTML for large sets function veryEfficientDOMUpdate(items) { const container = document.getElementById('container'); const html = items.map(item => `<div>${item.name}</div>`).join(''); container.innerHTML = html; }
Efficient Object Property Access
// ❌ Slow - dynamic property access in loop function slowPropertyAccess(objects, propertyName) { const results = []; for (const obj of objects) { results.push(obj[propertyName]); // Dynamic lookup each time } return results; } // ✅ Fast - cache property access pattern function fastPropertyAccess(objects, propertyName) { // For consistent object shapes, engines can optimize if (propertyName === 'name') { return objects.map(obj => obj.name); } else if (propertyName === 'age') { return objects.map(obj => obj.age); } // Fallback for dynamic properties return objects.map(obj => obj[propertyName]); } // Object shape consistency for V8 optimization class OptimizedUser { constructor(name, age, email) { // Always initialize all properties in same order this.name = name; this.age = age; this.email = email; // Avoid adding properties later - breaks hidden classes } }
Memory-Efficient Array Operations
// ❌ Memory intensive - creates many intermediate arrays function inefficientProcessing(largeArray) { return largeArray .filter(x => x > 10) .map(x => x * 2) .filter(x => x < 100) .map(x => x.toString()) .filter(x => x.length < 3); } // ✅ Memory efficient - single pass function efficientProcessing(largeArray) { const result = []; for (const x of largeArray) { if (x > 10) { const doubled = x * 2; if (doubled < 100) { const str = doubled.toString(); if (str.length < 3) { result.push(str); } } } } return result; } // ✅ Generator for lazy evaluation function* lazyProcessing(largeArray) { for (const x of largeArray) { if (x > 10) { const doubled = x * 2; if (doubled < 100) { const str = doubled.toString(); if (str.length < 3) { yield str; } } } } } // Use only what you need const first5Results = [...lazyProcessing(largeArray)].slice(0, 5);
Code Organization Best Practices
Module Design Patterns
// ✅ Well-structured module class DataService { constructor(apiUrl, options = {}) { this.apiUrl = apiUrl; this.timeout = options.timeout ?? 5000; this.retries = options.retries ?? 3; this.cache = new Map(); } async fetch(endpoint, options = {}) { const url = `${this.apiUrl}/${endpoint}`; const cacheKey = `${url}:${JSON.stringify(options)}`; // Check cache first if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } try { const data = await this._fetchWithRetry(url, options); this.cache.set(cacheKey, data); return data; } catch (error) { console.error(`Failed to fetch ${endpoint}:`, error); throw error; } } async _fetchWithRetry(url, options, attempt = 1) { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.timeout); const response = await fetch(url, { ...options, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return await response.json(); } catch (error) { if (attempt < this.retries) { const delay = Math.pow(2, attempt - 1) * 1000; // Exponential backoff await new Promise(resolve => setTimeout(resolve, delay)); return this._fetchWithRetry(url, options, attempt + 1); } throw error; } } clearCache() { this.cache.clear(); } } // Usage const api = new DataService('https://api.example.com', { timeout: 10000, retries: 2 });
Functional Programming Patterns
// Pure functions and immutability const UserUtils = { // Pure function - no side effects calculateAge(birthDate) { const today = new Date(); const birth = new Date(birthDate); return today.getFullYear() - birth.getFullYear(); }, // Immutable updates updateUser(user, updates) { return { ...user, ...updates, updatedAt: new Date().toISOString() }; }, // Compose functions compose(...fns) { return (value) => fns.reduceRight((acc, fn) => fn(acc), value); }, // Partial application partial(fn, ...args1) { return (...args2) => fn(...args1, ...args2); } }; // Usage const addTax = (rate, amount) => amount * (1 + rate); const addSalesTax = UserUtils.partial(addTax, 0.08); const formatCurrency = amount => `$${amount.toFixed(2)}`; const calculateTotal = UserUtils.compose(formatCurrency, addSalesTax); console.log(calculateTotal(100)); // "$108.00"
Performance Monitoring and Profiling
// Performance measurement utilities class PerformanceMonitor { constructor() { this.metrics = new Map(); } time(label) { this.metrics.set(label, performance.now()); } timeEnd(label) { const start = this.metrics.get(label); if (start) { const duration = performance.now() - start; console.log(`${label}: ${duration.toFixed(2)}ms`); this.metrics.delete(label); return duration; } } // Measure function execution time measure(fn, label = fn.name) { return (...args) => { this.time(label); const result = fn(...args); this.timeEnd(label); return result; }; } // Measure async function execution time measureAsync(fn, label = fn.name) { return async (...args) => { this.time(label); try { const result = await fn(...args); this.timeEnd(label); return result; } catch (error) { this.timeEnd(label); throw error; } }; } // Memory usage snapshot getMemoryUsage() { if (performance.memory) { return { used: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024), total: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024), limit: Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024) }; } return null; } } // Usage const monitor = new PerformanceMonitor(); const expensiveOperation = monitor.measure((data) => { return data.map(x => x * x).filter(x => x > 100); }); const result = expensiveOperation([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); console.log('Memory usage:', monitor.getMemoryUsage());
Security Best Practices
// Input sanitization and validation class InputValidator { static sanitizeString(input, maxLength = 255) { if (typeof input !== 'string') return ''; return input .trim() .slice(0, maxLength) .replace(/[<>]/g, ''); // Basic HTML tag removal } static validateEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } static validateURL(url) { try { const parsed = new URL(url); return ['http:', 'https:'].includes(parsed.protocol); } catch { return false; } } static escapeHTML(unsafe) { return unsafe .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } } // Secure API calls class SecureAPI { constructor(baseURL, token) { this.baseURL = baseURL; this.token = token; } async request(endpoint, options = {}) { const url = new URL(endpoint, this.baseURL); // Validate URL if (!InputValidator.validateURL(url.toString())) { throw new Error('Invalid URL'); } const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.token}`, 'X-Requested-With': 'XMLHttpRequest', // CSRF protection ...options.headers }; try { const response = await fetch(url, { ...options, headers, credentials: 'same-origin' // Prevent CSRF }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } catch (error) { console.error('API request failed:', error); throw error; } } }
Next Chapter: Chapter 16: Testing and Debugging
Previous Chapter: Chapter 14: Modern JavaScript Features (ES6+)
Table of Contents: JavaScript Guide
