Back to Articles

Chapter 15: Performance and Best Practices

July 27, 202513 min read
javascriptperformanceoptimizationmemory-managementerror-handlingbest-practices
Chapter 15: Performance and Best Practices

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, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/"/g, "&quot;")
            .replace(/'/g, "&#039;");
    }
}

// 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