Advanced JavaScript Patterns and Performance Optimization
Introduction
JavaScript performance optimization is crucial for building fast, responsive web applications. In this guide, we'll explore advanced patterns and techniques.
Memory Management
Avoiding Memory Leaks
javascript1// Common memory leak: Forgotten event listeners 2class Component { 3 constructor() { 4 this.handleClick = this.handleClick.bind(this); 5 document.addEventListener('click', this.handleClick); 6 } 7 8 // Missing cleanup! 9 // destructor() { 10 // document.removeEventListener('click', this.handleClick); 11 // } 12} 13 14// Proper cleanup with WeakMap 15const listeners = new WeakMap(); 16 17function addCleanupListener(element, event, handler) { 18 element.addEventListener(event, handler); 19 listeners.set(element, { event, handler }); 20} 21 22function cleanup(element) { 23 const listener = listeners.get(element); 24 if (listener) { 25 element.removeEventListener(listener.event, listener.handler); 26 listeners.delete(element); 27 } 28}
Performance Patterns
Debouncing and Throttling
javascript1function debounce(func, wait) { 2 let timeout; 3 return function executedFunction(...args) { 4 const later = () => { 5 clearTimeout(timeout); 6 func(...args); 7 }; 8 clearTimeout(timeout); 9 timeout = setTimeout(later, wait); 10 }; 11} 12 13function throttle(func, limit) { 14 let inThrottle; 15 return function(...args) { 16 if (!inThrottle) { 17 func.apply(this, args); 18 inThrottle = true; 19 setTimeout(() => inThrottle = false, limit); 20 } 21 }; 22} 23 24// Usage 25const searchInput = document.getElementById('search'); 26searchInput.addEventListener('input', debounce(search, 300)); 27window.addEventListener('scroll', throttle(updatePosition, 100));
Virtual Scrolling
javascript1class VirtualScroll { 2 constructor(container, items, itemHeight) { 3 this.container = container; 4 this.items = items; 5 this.itemHeight = itemHeight; 6 this.visibleCount = Math.ceil(container.clientHeight / itemHeight); 7 8 this.renderChunk(0); 9 container.addEventListener('scroll', () => this.handleScroll()); 10 } 11 12 renderChunk(startIndex) { 13 const endIndex = Math.min(startIndex + this.visibleCount, this.items.length); 14 15 // Reuse existing elements 16 this.container.innerHTML = ''; 17 18 for (let i = startIndex; i < endIndex; i++) { 19 const item = document.createElement('div'); 20 item.style.position = 'absolute'; 21 item.style.top = `${i * this.itemHeight}px`; 22 item.textContent = this.items[i]; 23 this.container.appendChild(item); 24 } 25 26 // Set container height for proper scrolling 27 this.container.style.height = `${this.items.length * this.itemHeight}px`; 28 } 29 30 handleScroll() { 31 const scrollTop = this.container.scrollTop; 32 const startIndex = Math.floor(scrollTop / this.itemHeight); 33 this.renderChunk(startIndex); 34 } 35}
Modern JavaScript Features
Private Class Fields
javascript1class ApiService { 2 #apiKey = process.env.API_KEY; 3 #cache = new Map(); 4 5 async #fetchWithCache(url) { 6 if (this.#cache.has(url)) { 7 return this.#cache.get(url); 8 } 9 10 const response = await fetch(url, { 11 headers: { Authorization: `Bearer ${this.#apiKey}` } 12 }); 13 const data = await response.json(); 14 this.#cache.set(url, data); 15 return data; 16 } 17 18 // Public method 19 async getData(endpoint) { 20 return this.#fetchWithCache(`https://api.example.com/${endpoint}`); 21 } 22}
Proxy for Advanced Patterns
javascript1const validationHandler = { 2 set(target, property, value) { 3 if (property === 'age' && (typeof value !== 'number' || value < 0)) { 4 throw new Error('Age must be a positive number'); 5 } 6 if (property === 'email' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) { 7 throw new Error('Invalid email address'); 8 } 9 target[property] = value; 10 return true; 11 } 12}; 13 14const user = new Proxy({}, validationHandler); 15user.name = 'John'; // OK 16user.age = 25; // OK 17user.age = -5; // Error: Age must be a positive number
Web Workers for CPU-Intensive Tasks
javascript1// main.js 2const worker = new Worker('worker.js'); 3 4worker.onmessage = (event) => { 5 console.log('Result:', event.data); 6}; 7 8worker.postMessage({ 9 type: 'process-data', 10 data: largeDataset 11}); 12 13// worker.js 14self.onmessage = (event) => { 15 if (event.data.type === 'process-data') { 16 // CPU-intensive processing 17 const result = processData(event.data.data); 18 self.postMessage(result); 19 } 20}; 21 22function processData(data) { 23 // Heavy computation here 24 return data.map(item => expensiveOperation(item)); 25}
Performance Monitoring
javascript1class PerformanceMonitor { 2 constructor() { 3 this.metrics = new Map(); 4 this.startTime = performance.now(); 5 6 // Monitor long tasks 7 if ('PerformanceObserver' in window) { 8 const observer = new PerformanceObserver((list) => { 9 list.getEntries().forEach(entry => { 10 if (entry.duration > 50) { // Tasks longer than 50ms 11 console.warn('Long task detected:', entry); 12 this.reportLongTask(entry); 13 } 14 }); 15 }); 16 observer.observe({ entryTypes: ['longtask'] }); 17 } 18 } 19 20 measure(name, fn) { 21 const start = performance.now(); 22 const result = fn(); 23 const duration = performance.now() - start; 24 25 this.metrics.set(name, { 26 duration, 27 timestamp: Date.now() 28 }); 29 30 if (duration > 100) { 31 console.warn(`Slow operation: ${name} took ${duration}ms`); 32 } 33 34 return result; 35 } 36}