Menu

HTML & JS Topic

JavaScript Performance Optimization

1. DOM Performance

Minimize DOM Access

javascript

//  BAD: Multiple DOM queries in loop
for (let i = 0; i < 100; i++) {
    document.querySelector('.item').textContent = i; // Queries DOM 100 times!
}

//  GOOD: Cache DOM reference
const item = document.querySelector('.item'); // Query once
for (let i = 0; i < 100; i++) {
    item.textContent = i; // Updates cached reference
}

//  BAD: Reading layout properties causes reflows
function resizeElements() {
    const elements = document.querySelectorAll('.box');
    elements.forEach(el => {
        const width = el.offsetWidth; // Forces layout
        el.style.width = (width + 10) + 'px'; // Causes reflow
    });
}

//  GOOD: Batch read/write operations
function resizeElementsOptimized() {
    const elements = document.querySelectorAll('.box');
    
    // 1. Read all properties first
    const widths = Array.from(elements).map(el => el.offsetWidth);
    
    // 2. Write all changes (in single reflow)
    elements.forEach((el, i) => {
        el.style.width = (widths[i] + 10) + 'px';
    });
}

Document Fragments for Bulk Updates

javascript

// BAD: Appending directly to DOM in loop
function addItemsDirect(items) {
    const container = document.getElementById('container');
    items.forEach(item => {
        const div = document.createElement('div');
        div.textContent = item;
        container.appendChild(div); // Reflow each iteration
    });
}

//  GOOD: Use DocumentFragment
function addItemsOptimized(items) {
    const container = document.getElementById('container');
    const fragment = document.createDocumentFragment(); // Lightweight container
    
    items.forEach(item => {
        const div = document.createElement('div');
        div.textContent = item;
        fragment.appendChild(div); // No reflow here
    });
    
    container.appendChild(fragment); // Single reflow
}

//  BETTER: Use innerHTML with string building
function addItemsBest(items) {
    const container = document.getElementById('container');
    let html = '';
    
    items.forEach(item => {
        html += `<div>${item}</div>`;
    });
    
    container.innerHTML = html; // Single reflow
}

Virtual DOM Pattern

javascript

// Simple Virtual DOM implementation
class VirtualDOM {
    constructor() {
        this.cache = new Map();
    }
    
    // Store element state
    updateElement(id, newState) {
        const oldState = this.cache.get(id);
        const differences = this.diff(oldState, newState);
        
        if (differences.length > 0) {
            this.applyChanges(id, differences);
            this.cache.set(id, newState);
        }
    }
    
    // Find differences
    diff(oldState, newState) {
        const changes = [];
        
        if (!oldState) {
            return ['full-update']; // First render
        }
        
        // Compare properties
        for (const key in newState) {
            if (oldState[key] !== newState[key]) {
                changes.push({ key, value: newState[key] });
            }
        }
        
        return changes;
    }
    
    // Apply minimal changes
    applyChanges(elementId, changes) {
        const element = document.getElementById(elementId);
        
        changes.forEach(change => {
            if (change === 'full-update') {
                // Full update logic
            } else {
                element[change.key] = change.value;
            }
        });
    }
}

// Usage
const vdom = new VirtualDOM();

// Update only changed properties
vdom.updateElement('user-card', {
    textContent: 'John Updated',
    className: 'user active',
    'data-id': 123
});

Event Delegation

javascript

//  BAD: Event listener on each element
const buttons = document.querySelectorAll('.btn');
buttons.forEach(btn => {
    btn.addEventListener('click', handleClick); // Multiple listeners
});

//  GOOD: Single event listener on parent
document.getElementById('container').addEventListener('click', (event) => {
    if (event.target.matches('.btn')) {
        handleClick(event);
    }
});

//  BETTER: For dynamic content
class EventDelegator {
    constructor() {
        this.handlers = new Map();
    }
    
    addDelegatedListener(container, selector, eventType, handler) {
        const key = `${eventType}-${selector}`;
        
        if (!this.handlers.has(key)) {
            const listener = (event) => {
                if (event.target.matches(selector)) {
                    handler(event);
                }
                // Check parent elements too (for nested content)
                let element = event.target;
                while (element && element !== container) {
                    if (element.matches(selector)) {
                        handler.call(element, event);
                        break;
                    }
                    element = element.parentElement;
                }
            };
            
            container.addEventListener(eventType, listener);
            this.handlers.set(key, listener);
        }
    }
    
    removeDelegatedListener(container, selector, eventType) {
        const key = `${eventType}-${selector}`;
        const listener = this.handlers.get(key);
        if (listener) {
            container.removeEventListener(eventType, listener);
            this.handlers.delete(key);
        }
    }
}

// Usage
const delegator = new EventDelegator();
const container = document.getElementById('dynamic-content');

// Add delegated listener
delegator.addDelegatedListener(container, '.btn', 'click', function(event) {
    console.log('Button clicked:', this.textContent);
});

2. Debouncing & Throttling

Debouncing Implementation

javascript

// Basic debounce
function debounce(func, wait) {
    let timeout;
    
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func.apply(this, args);
        };
        
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

// Advanced debounce with immediate option
function advancedDebounce(func, wait, immediate = false) {
    let timeout;
    let result;
    
    return function(...args) {
        const context = this;
        
        const later = function() {
            timeout = null;
            if (!immediate) {
                result = func.apply(context, args);
            }
        };
        
        const callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        
        if (callNow) {
            result = func.apply(context, args);
        }
        
        return result;
    };
}

// Usage examples
// Search input
const searchInput = document.getElementById('search');
const performSearch = debounce(function(query) {
    console.log('Searching for:', query);
    // API call here
}, 300);

searchInput.addEventListener('input', (e) => {
    performSearch(e.target.value);
});

// Window resize
const handleResize = debounce(function() {
    console.log('Window resized to:', window.innerWidth, 'x', window.innerHeight);
    // Update layout
}, 250);

window.addEventListener('resize', handleResize);

// Form validation on blur
const validateField = debounce(function(value) {
    console.log('Validating:', value);
    // Validation logic
}, 500);

document.getElementById('email').addEventListener('input', (e) => {
    validateField(e.target.value);
});

Throttling Implementation

javascript

// Basic throttle
function throttle(func, limit) {
    let inThrottle;
    
    return function(...args) {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}

// Advanced throttle with trailing call
function advancedThrottle(func, limit, options = {}) {
    let lastCall = 0;
    let timeout;
    let lastArgs;
    let lastContext;
    
    const { leading = true, trailing = true } = options;
    
    return function(...args) {
        const context = this;
        const now = Date.now();
        
        if (!lastCall && !leading) {
            lastCall = now;
        }
        
        const remaining = limit - (now - lastCall);
        
        if (remaining <= 0 || remaining > limit) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            lastCall = now;
            func.apply(context, args);
        } else if (!timeout && trailing) {
            timeout = setTimeout(() => {
                lastCall = leading ? Date.now() : 0;
                timeout = null;
                if (lastArgs) {
                    func.apply(lastContext, lastArgs);
                    lastArgs = lastContext = null;
                }
            }, remaining);
            
            lastArgs = args;
            lastContext = context;
        }
    };
}

// Usage examples
// Scroll events
const handleScroll = throttle(function() {
    const scrollTop = window.pageYOffset;
    console.log('Scroll position:', scrollTop);
    
    // Update sticky header, lazy load, etc.
    updateHeaderVisibility(scrollTop);
}, 100);

window.addEventListener('scroll', handleScroll);

// Mouse move tracking
const trackMouse = throttle(function(event) {
    console.log('Mouse at:', event.clientX, event.clientY);
    // Update cursor follower or tooltip
}, 50);

document.addEventListener('mousemove', trackMouse);

// Game loop or animation
const gameUpdate = throttle(function() {
    // Update game state
    updatePhysics();
    renderFrame();
}, 16); // ~60 FPS

Debounce vs Throttle Visualizer

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Debounce vs Throttle</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
        }
        
        header {
            text-align: center;
            margin-bottom: 40px;
        }
        
        h1 {
            font-size: 2.5rem;
            margin-bottom: 10px;
        }
        
        .controls {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 20px;
            margin-bottom: 40px;
        }
        
        .control-card {
            background: rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(10px);
            border-radius: 15px;
            padding: 25px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
        }
        
        .control-card h3 {
            font-size: 1.5rem;
            margin-bottom: 15px;
            color: #ffcc00;
        }
        
        .slider-container {
            margin: 20px 0;
        }
        
        .slider-container label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
        }
        
        .slider-value {
            float: right;
            font-weight: bold;
        }
        
        input[type="range"] {
            width: 100%;
            height: 10px;
            border-radius: 5px;
            background: rgba(255, 255, 255, 0.2);
            outline: none;
            -webkit-appearance: none;
        }
        
        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: #ffcc00;
            cursor: pointer;
        }
        
        .visualizer {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 20px;
            margin-bottom: 40px;
        }
        
        .chart-container {
            background: rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(10px);
            border-radius: 15px;
            padding: 20px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
        }
        
        .chart {
            height: 200px;
            position: relative;
            margin-top: 20px;
        }
        
        .event-bar {
            position: absolute;
            bottom: 0;
            width: 4px;
            background: rgba(255, 255, 255, 0.5);
        }
        
        .execution-bar {
            position: absolute;
            bottom: 30px;
            width: 8px;
            background: #ff4444;
            border-radius: 4px;
        }
        
        .stats {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
            margin-top: 30px;
        }
        
        .stat-card {
            background: rgba(255, 255, 255, 0.1);
            border-radius: 10px;
            padding: 15px;
            text-align: center;
        }
        
        .stat-value {
            font-size: 2rem;
            font-weight: bold;
            color: #ffcc00;
        }
        
        .stat-label {
            font-size: 0.9rem;
            opacity: 0.8;
        }
        
        .legend {
            display: flex;
            gap: 20px;
            justify-content: center;
            margin-top: 20px;
        }
        
        .legend-item {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .legend-color {
            width: 15px;
            height: 15px;
            border-radius: 3px;
        }
        
        .event-color {
            background: rgba(255, 255, 255, 0.5);
        }
        
        .debounce-color {
            background: #4CAF50;
        }
        
        .throttle-color {
            background: #2196F3;
        }
        
        button {
            background: #ffcc00;
            color: #333;
            border: none;
            padding: 12px 24px;
            border-radius: 25px;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
            transition: transform 0.2s, box-shadow 0.2s;
            width: 100%;
            margin-top: 20px;
        }
        
        button:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(255, 204, 0, 0.4);
        }
        
        .description {
            margin-top: 30px;
            line-height: 1.6;
            background: rgba(255, 255, 255, 0.1);
            padding: 20px;
            border-radius: 10px;
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>⚡ Debounce vs Throttle Visualizer</h1>
            <p>See the difference between these two performance optimization techniques</p>
        </header>
        
        <div class="controls">
            <div class="control-card">
                <h3>🎮 Simulate Events</h3>
                <p>Move mouse in this area to generate events:</p>
                <div id="event-area" style="
                    height: 150px;
                    background: rgba(255, 255, 255, 0.1);
                    border-radius: 10px;
                    margin: 15px 0;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    font-size: 1.2rem;
                    cursor: crosshair;
                ">
                    Move Mouse Here
                </div>
                <button onclick="simulateRapidEvents()">Simulate Rapid Events</button>
                <button onclick="resetVisualizer()" style="background: #ff4444; color: white;">Reset</button>
            </div>
            
            <div class="control-card">
                <h3>⚙️ Configuration</h3>
                
                <div class="slider-container">
                    <label>
                        <span>Debounce Delay</span>
                        <span class="slider-value" id="debounce-value">300ms</span>
                    </label>
                    <input type="range" id="debounce-slider" min="0" max="1000" value="300" step="10">
                </div>
                
                <div class="slider-container">
                    <label>
                        <span>Throttle Interval</span>
                        <span class="slider-value" id="throttle-value">100ms</span>
                    </label>
                    <input type="range" id="throttle-slider" min="0" max="1000" value="100" step="10">
                </div>
                
                <div class="slider-container">
                    <label>
                        <span>Event Rate (Simulation)</span>
                        <span class="slider-value" id="rate-value">20ms</span>
                    </label>
                    <input type="range" id="rate-slider" min="1" max="100" value="20" step="1">
                </div>
            </div>
        </div>
        
        <div class="visualizer">
            <div class="chart-container">
                <h3>📈 Raw Events</h3>
                <div class="chart" id="raw-chart"></div>
                <div class="stats">
                    <div class="stat-card">
                        <div class="stat-value" id="raw-count">0</div>
                        <div class="stat-label">Total Events</div>
                    </div>
                    <div class="stat-card">
                        <div class="stat-value" id="raw-rate">0</div>
                        <div class="stat-label">Events/Second</div>
                    </div>
                </div>
            </div>
            
            <div class="chart-container">
                <h3>🎯 Debounced Events</h3>
                <div class="chart" id="debounce-chart"></div>
                <div class="stats">
                    <div class="stat-card">
                        <div class="stat-value" id="debounce-count">0</div>
                        <div class="stat-label">Executions</div>
                    </div>
                    <div class="stat-card">
                        <div class="stat-value" id="debounce-savings">0%</div>
                        <div class="stat-label">Reduction</div>
                    </div>
                </div>
            </div>
            
            <div class="chart-container">
                <h3>⚡ Throttled Events</h3>
                <div class="chart" id="throttle-chart"></div>
                <div class="stats">
                    <div class="stat-card">
                        <div class="stat-value" id="throttle-count">0</div>
                        <div class="stat-label">Executions</div>
                    </div>
                    <div class="stat-card">
                        <div class="stat-value" id="throttle-savings">0%</div>
                        <div class="stat-label">Reduction</div>
                    </div>
                </div>
            </div>
        </div>
        
        <div class="legend">
            <div class="legend-item">
                <div class="legend-color event-color"></div>
                <span>Event Triggered</span>
            </div>
            <div class="legend-item">
                <div class="legend-color debounce-color"></div>
                <span>Debounced Execution</span>
            </div>
            <div class="legend-item">
                <div class="legend-color throttle-color"></div>
                <span>Throttled Execution</span>
            </div>
        </div>
        
        <div class="description">
            <h3>📖 When to Use Each:</h3>
            <p><strong>Debouncing</strong> groups multiple sequential calls into a single execution. Use for: search inputs, window resize, form validation.</p>
            <p><strong>Throttling</strong> guarantees execution at regular intervals. Use for: scrolling, mouse movements, game loops, API rate limiting.</p>
        </div>
    </div>
    
    <script>
        class PerformanceVisualizer {
            constructor() {
                this.rawEvents = [];
                this.debouncedExecutions = [];
                this.throttledExecutions = [];
                this.eventCount = 0;
                this.debounceCount = 0;
                this.throttleCount = 0;
                this.startTime = Date.now();
                
                // Get configuration elements
                this.debounceSlider = document.getElementById('debounce-slider');
                this.throttleSlider = document.getElementById('throttle-slider');
                this.rateSlider = document.getElementById('rate-slider');
                
                // Initialize charts
                this.rawChart = document.getElementById('raw-chart');
                this.debounceChart = document.getElementById('debounce-chart');
                this.throttleChart = document.getElementById('throttle-chart');
                
                // Setup event listeners
                this.setupSliders();
                this.setupEventArea();
                this.setupDebounceThrottle();
                
                // Start update loop
                this.updateCharts();
                this.updateStats();
            }
            
            setupSliders() {
                // Update slider values display
                this.debounceSlider.addEventListener('input', (e) => {
                    document.getElementById('debounce-value').textContent = e.target.value + 'ms';
                    this.setupDebounceThrottle();
                });
                
                this.throttleSlider.addEventListener('input', (e) => {
                    document.getElementById('throttle-value').textContent = e.target.value + 'ms';
                    this.setupDebounceThrottle();
                });
                
                this.rateSlider.addEventListener('input', (e) => {
                    document.getElementById('rate-value').textContent = e.target.value + 'ms';
                });
            }
            
            setupEventArea() {
                const eventArea = document.getElementById('event-area');
                let mouseMoveTimeout;
                
                eventArea.addEventListener('mousemove', (e) => {
                    // Record event
                    this.recordEvent('mouse');
                    
                    // Debounced handler
                    this.debouncedHandler();
                    
                    // Throttled handler
                    this.throttledHandler();
                    
                    // Visual feedback
                    clearTimeout(mouseMoveTimeout);
                    eventArea.style.background = 'rgba(255, 204, 0, 0.2)';
                    mouseMoveTimeout = setTimeout(() => {
                        eventArea.style.background = 'rgba(255, 255, 255, 0.1)';
                    }, 100);
                });
            }
            
            setupDebounceThrottle() {
                const debounceDelay = parseInt(this.debounceSlider.value);
                const throttleInterval = parseInt(this.throttleSlider.value);
                
                // Create debounced function
                this.debouncedHandler = this.debounce(() => {
                    this.recordDebouncedExecution();
                }, debounceDelay);
                
                // Create throttled function
                this.throttledHandler = this.throttle(() => {
                    this.recordThrottledExecution();
                }, throttleInterval);
            }
            
            // Debounce implementation
            debounce(func, wait) {
                let timeout;
                return function executedFunction(...args) {
                    const later = () => {
                        clearTimeout(timeout);
                        func.apply(this, args);
                    };
                    clearTimeout(timeout);
                    timeout = setTimeout(later, wait);
                };
            }
            
            // Throttle implementation
            throttle(func, limit) {
                let inThrottle;
                return function(...args) {
                    if (!inThrottle) {
                        func.apply(this, args);
                        inThrottle = true;
                        setTimeout(() => inThrottle = false, limit);
                    }
                };
            }
            
            recordEvent(source) {
                this.eventCount++;
                this.rawEvents.push({
                    time: Date.now(),
                    source
                });
                
                // Keep only recent events for visualization
                const oneSecondAgo = Date.now() - 1000;
                this.rawEvents = this.rawEvents.filter(event => event.time > oneSecondAgo);
            }
            
            recordDebouncedExecution() {
                this.debounceCount++;
                this.debouncedExecutions.push({
                    time: Date.now(),
                    type: 'debounce'
                });
                
                // Keep only recent executions
                const fiveSecondsAgo = Date.now() - 5000;
                this.debouncedExecutions = this.debouncedExecutions.filter(exec => exec.time > fiveSecondsAgo);
            }
            
            recordThrottledExecution() {
                this.throttleCount++;
                this.throttledExecutions.push({
                    time: Date.now(),
                    type: 'throttle'
                });
                
                // Keep only recent executions
                const fiveSecondsAgo = Date.now() - 5000;
                this.throttledExecutions = this.throttledExecutions.filter(exec => exec.time > fiveSecondsAgo);
            }
            
            simulateRapidEvents() {
                const eventCount = 100;
                const interval = parseInt(this.rateSlider.value);
                
                for (let i = 0; i < eventCount; i++) {
                    setTimeout(() => {
                        this.recordEvent('simulation');
                        this.debouncedHandler();
                        this.throttledHandler();
                    }, i * interval);
                }
            }
            
            updateCharts() {
                // Clear charts
                this.rawChart.innerHTML = '';
                this.debounceChart.innerHTML = '';
                this.throttleChart.innerHTML = '';
                
                const now = Date.now();
                const chartWidth = this.rawChart.clientWidth;
                const fiveSeconds = 5000;
                
                // Draw raw events
                this.rawEvents.forEach(event => {
                    const position = ((event.time - (now - fiveSeconds)) / fiveSeconds) * chartWidth;
                    if (position >= 0 && position <= chartWidth) {
                        const bar = document.createElement('div');
                        bar.className = 'event-bar';
                        bar.style.left = position + 'px';
                        bar.style.height = '40px';
                        this.rawChart.appendChild(bar);
                    }
                });
                
                // Draw debounced executions
                this.debouncedExecutions.forEach(exec => {
                    const position = ((exec.time - (now - fiveSeconds)) / fiveSeconds) * chartWidth;
                    if (position >= 0 && position <= chartWidth) {
                        const bar = document.createElement('div');
                        bar.className = 'execution-bar';
                        bar.style.left = position + 'px';
                        bar.style.height = '60px';
                        bar.style.background = '#4CAF50';
                        this.debounceChart.appendChild(bar);
                    }
                });
                
                // Draw throttled executions
                this.throttledExecutions.forEach(exec => {
                    const position = ((exec.time - (now - fiveSeconds)) / fiveSeconds) * chartWidth;
                    if (position >= 0 && position <= chartWidth) {
                        const bar = document.createElement('div');
                        bar.className = 'execution-bar';
                        bar.style.left = position + 'px';
                        bar.style.height = '60px';
                        bar.style.background = '#2196F3';
                        this.throttleChart.appendChild(bar);
                    }
                });
                
                // Schedule next update
                requestAnimationFrame(() => this.updateCharts());
            }
            
            updateStats() {
                const now = Date.now();
                const elapsedSeconds = (now - this.startTime) / 1000;
                
                // Calculate events per second (last 1 second)
                const oneSecondAgo = now - 1000;
                const recentEvents = this.rawEvents.filter(event => event.time > oneSecondAgo);
                const eventsPerSecond = recentEvents.length;
                
                // Update displays
                document.getElementById('raw-count').textContent = this.eventCount;
                document.getElementById('raw-rate').textContent = eventsPerSecond;
                
                document.getElementById('debounce-count').textContent = this.debounceCount;
                document.getElementById('throttle-count').textContent = this.throttleCount;
                
                // Calculate savings
                if (this.eventCount > 0) {
                    const debounceSavings = Math.round((1 - this.debounceCount / this.eventCount) * 100);
                    const throttleSavings = Math.round((1 - this.throttleCount / this.eventCount) * 100);
                    
                    document.getElementById('debounce-savings').textContent = debounceSavings + '%';
                    document.getElementById('throttle-savings').textContent = throttleSavings + '%';
                }
                
                // Schedule next update
                setTimeout(() => this.updateStats(), 1000);
            }
            
            resetVisualizer() {
                this.rawEvents = [];
                this.debouncedExecutions = [];
                this.throttledExecutions = [];
                this.eventCount = 0;
                this.debounceCount = 0;
                this.throttleCount = 0;
                this.startTime = Date.now();
            }
        }
        
        // Initialize visualizer
        const visualizer = new PerformanceVisualizer();
        
        // Expose simulation function globally
        window.simulateRapidEvents = () => visualizer.simulateRapidEvents();
        window.resetVisualizer = () => visualizer.resetVisualizer();
    </script>
</body>
</html>

3. Lazy Loading

Image Lazy Loading

javascript

// Intersection Observer for lazy loading
class LazyLoader {
    constructor(options = {}) {
        this.options = {
            root: null,
            rootMargin: '50px',
            threshold: 0.01,
            ...options
        };
        
        this.observer = new IntersectionObserver(
            this.handleIntersection.bind(this),
            this.options
        );
        
        this.observedElements = new Map();
    }
    
    observe(element, callback) {
        if (element.dataset.lazyObserved) {
            return; // Already observing
        }
        
        element.dataset.lazyObserved = 'true';
        this.observedElements.set(element, callback);
        this.observer.observe(element);
    }
    
    unobserve(element) {
        if (element.dataset.lazyObserved) {
            this.observer.unobserve(element);
            this.observedElements.delete(element);
            delete element.dataset.lazyObserved;
        }
    }
    
    handleIntersection(entries) {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                const element = entry.target;
                const callback = this.observedElements.get(element);
                
                if (callback) {
                    callback(element);
                    this.unobserve(element);
                }
            }
        });
    }
    
    disconnect() {
        this.observer.disconnect();
        this.observedElements.clear();
    }
}

// Image lazy loading implementation
class LazyImageLoader extends LazyLoader {
    constructor(options = {}) {
        super(options);
    }
    
    addImage(imgElement) {
        if (!imgElement.dataset.src) {
            console.warn('Image element missing data-src attribute');
            return;
        }
        
        // Set placeholder or low-quality image
        if (imgElement.dataset.lowSrc) {
            imgElement.src = imgElement.dataset.lowSrc;
        } else {
            imgElement.src = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="%WIDTH%" height="%HEIGHT%" viewBox="0 0 %WIDTH% %HEIGHT%"%3E%3Crect width="100%" height="100%" fill="%23f0f0f0"/%3E%3C/svg%3E'
                .replace(/%WIDTH%/g, imgElement.width || 300)
                .replace(/%HEIGHT%/g, imgElement.height || 200);
        }
        
        // Add loading class for styling
        imgElement.classList.add('lazy-loading');
        
        // Observe element
        this.observe(imgElement, (element) => {
            this.loadImage(element);
        });
    }
    
    loadImage(imgElement) {
        const src = imgElement.dataset.src;
        const srcset = imgElement.dataset.srcset;
        
        // Create new image to preload
        const tempImage = new Image();
        
        tempImage.onload = () => {
            // Apply the real image
            imgElement.src = src;
            if (srcset) {
                imgElement.srcset = srcset;
            }
            
            // Remove loading class, add loaded class
            imgElement.classList.remove('lazy-loading');
            imgElement.classList.add('lazy-loaded');
            
            // Dispatch custom event
            imgElement.dispatchEvent(new CustomEvent('lazyloaded', {
                detail: { element: imgElement }
            }));
        };
        
        tempImage.onerror = () => {
            console.error('Failed to load image:', src);
            imgElement.classList.remove('lazy-loading');
            imgElement.classList.add('lazy-error');
            
            // Fallback to error image if specified
            if (imgElement.dataset.errorSrc) {
                imgElement.src = imgElement.dataset.errorSrc;
            }
        };
        
        // Start loading
        tempImage.src = src;
        if (srcset) {
            tempImage.srcset = srcset;
        }
    }
    
    // Initialize all images on page
    initAllImages() {
        document.querySelectorAll('img[data-src]').forEach(img => {
            this.addImage(img);
        });
    }
}

// Usage
const imageLoader = new LazyImageLoader();
document.addEventListener('DOMContentLoaded', () => {
    imageLoader.initAllImages();
});

// For dynamically added images
function addNewImage(src, container) {
    const img = document.createElement('img');
    img.dataset.src = src;
    img.width = 300;
    img.height = 200;
    img.alt = 'Lazy loaded image';
    
    container.appendChild(img);
    imageLoader.addImage(img);
}

Component Lazy Loading

javascript

// Lazy load JavaScript components
class ComponentLoader {
    constructor() {
        this.components = new Map();
        this.loadedComponents = new Set();
        this.observer = new IntersectionObserver(
            this.handleComponentIntersection.bind(this),
            { rootMargin: '100px', threshold: 0.1 }
        );
    }
    
    registerComponent(name, loader) {
        this.components.set(name, loader);
    }
    
    observeComponent(element, componentName) {
        if (!this.components.has(componentName)) {
            console.warn(`Component "${componentName}" not registered`);
            return;
        }
        
        element.dataset.component = componentName;
        this.observer.observe(element);
    }
    
    async handleComponentIntersection(entries) {
        for (const entry of entries) {
            if (entry.isIntersecting) {
                const element = entry.target;
                const componentName = element.dataset.component;
                
                if (componentName && !this.loadedComponents.has(componentName)) {
                    await this.loadComponent(componentName, element);
                    this.observer.unobserve(element);
                }
            }
        }
    }
    
    async loadComponent(name, container) {
        console.log(`Loading component: ${name}`);
        
        const loader = this.components.get(name);
        if (!loader) {
            throw new Error(`Loader not found for component: ${name}`);
        }
        
        try {
            // Show loading state
            container.classList.add('component-loading');
            
            // Load component
            const component = await loader();
            
            // Initialize component in container
            if (typeof component === 'function') {
                component(container);
            } else if (component.default && typeof component.default === 'function') {
                component.default(container);
            } else {
                container.innerHTML = component;
            }
            
            // Update state
            container.classList.remove('component-loading');
            container.classList.add('component-loaded');
            this.loadedComponents.add(name);
            
            console.log(`Component "${name}" loaded successfully`);
            
        } catch (error) {
            console.error(`Failed to load component "${name}":`, error);
            container.classList.add('component-error');
            container.innerHTML = `
                <div class="error-message">
                    Failed to load component: ${name}
                </div>
            `;
        }
    }
    
    // Initialize all components on page
    initComponents() {
        document.querySelectorAll('[data-component]').forEach(element => {
            const componentName = element.dataset.component;
            this.observeComponent(element, componentName);
        });
    }
}

// Usage example
const componentLoader = new ComponentLoader();

// Register components
componentLoader.registerComponent('chart', async () => {
    // Dynamically import chart library
    const { Chart } = await import('https://cdn.jsdelivr.net/npm/chart.js');
    return (container) => {
        const ctx = container.querySelector('canvas').getContext('2d');
        new Chart(ctx, {
            type: 'bar',
            data: {
                labels: ['A', 'B', 'C', 'D'],
                datasets: [{
                    label: 'Data',
                    data: [12, 19, 3, 5],
                    backgroundColor: 'rgba(75, 192, 192, 0.2)',
                    borderColor: 'rgba(75, 192, 192, 1)',
                    borderWidth: 1
                }]
            }
        });
    };
});

componentLoader.registerComponent('map', async () => {
    // Load map component
    const mapModule = await import('./components/map.js');
    return mapModule.default;
});

componentLoader.registerComponent('comments', async () => {
    // Load comments from API
    const response = await fetch('/api/comments');
    const comments = await response.json();
    return (container) => {
        container.innerHTML = comments.map(comment => `
            <div class="comment">
                <h4>${comment.author}</h4>
                <p>${comment.text}</p>
            </div>
        `).join('');
    };
});

// Initialize on page load
document.addEventListener('DOMContentLoaded', () => {
    componentLoader.initComponents();
});

4. Code Splitting

Dynamic Imports (ES Modules)

javascript

// Basic dynamic import
button.addEventListener('click', async () => {
    try {
        // Load module on demand
        const module = await import('./heavy-module.js');
        module.doSomething();
    } catch (error) {
        console.error('Failed to load module:', error);
    }
});

// Route-based code splitting (for SPAs)
class RouteManager {
    constructor() {
        this.routes = new Map();
        this.loadedRoutes = new Set();
    }
    
    addRoute(path, modulePath) {
        this.routes.set(path, modulePath);
    }
    
    async navigate(path) {
        if (this.loadedRoutes.has(path)) {
            // Route already loaded
            return;
        }
        
        const modulePath = this.routes.get(path);
        if (!modulePath) {
            throw new Error(`Route not found: ${path}`);
        }
        
        try {
            // Show loading indicator
            this.showLoading();
            
            // Dynamically import route module
            const module = await import(modulePath);
            
            // Initialize route
            if (module.default && typeof module.default === 'function') {
                await module.default();
            }
            
            // Update state
            this.loadedRoutes.add(path);
            this.hideLoading();
            
        } catch (error) {
            console.error(`Failed to load route ${path}:`, error);
            this.showError(`Failed to load page`);
        }
    }
    
    showLoading() {
        // Show loading spinner
        const loader = document.createElement('div');
        loader.id = 'route-loader';
        loader.innerHTML = `
            <div class="spinner"></div>
            <p>Loading...</p>
        `;
        document.body.appendChild(loader);
    }
    
    hideLoading() {
        const loader = document.getElementById('route-loader');
        if (loader) {
            loader.remove();
        }
    }
    
    showError(message) {
        // Show error message
        const errorDiv = document.createElement('div');
        errorDiv.className = 'route-error';
        errorDiv.textContent = message;
        document.body.appendChild(errorDiv);
    }
}

// Usage
const routeManager = new RouteManager();
routeManager.addRoute('/dashboard', './routes/dashboard.js');
routeManager.addRoute('/analytics', './routes/analytics.js');
routeManager.addRoute('/settings', './routes/settings.js');

// Navigation
document.querySelectorAll('.nav-link').forEach(link => {
    link.addEventListener('click', (e) => {
        e.preventDefault();
        const path = link.getAttribute('href');
        routeManager.navigate(path);
    });
});

Webpack-Style Code Splitting

javascript

// With Webpack magic comments (for bundlers)
const loadModule = (moduleName) => {
    return import(
        /* webpackChunkName: "[request]" */
        /* webpackPrefetch: true */
        /* webpackPreload: true */
        `./modules/${moduleName}`
    );
};

// Component-based splitting
const ComponentLoader = {
    cache: new Map(),
    
    async load(componentName) {
        // Check cache first
        if (this.cache.has(componentName)) {
            return this.cache.get(componentName);
        }
        
        // Dynamic import with error handling
        try {
            const module = await import(
                /* webpackChunkName: "component-[request]" */
                `./components/${componentName}.js`
            );
            
            // Cache the module
            this.cache.set(componentName, module);
            return module;
            
        } catch (error) {
            console.error(`Failed to load component ${componentName}:`, error);
            
            // Return fallback component
            return {
                default: (container) => {
                    container.innerHTML = `
                        <div class="component-error">
                            Failed to load component: ${componentName}
                        </div>
                    `;
                }
            };
        }
    }
};

// Lazy load based on user interaction
class PredictiveLoader {
    constructor() {
        this.observers = new Map();
        this.predictedModules = new Set();
        this.preloadedModules = new Set();
    }
    
    // Observe user behavior to predict what to load
    observeUserBehavior(element, moduleName) {
        const observer = new IntersectionObserver(
            (entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        this.preloadModule(moduleName);
                    }
                });
            },
            { rootMargin: '200px', threshold: 0.1 }
        );
        
        observer.observe(element);
        this.observers.set(moduleName, observer);
    }
    
    // Preload module without executing
    async preloadModule(moduleName) {
        if (this.preloadedModules.has(moduleName)) {
            return;
        }
        
        console.log(`Preloading module: ${moduleName}`);
        
        try {
            // Use link rel="preload" for CSS/JS
            if (moduleName.endsWith('.css')) {
                this.preloadCSS(moduleName);
            } else if (moduleName.endsWith('.js')) {
                await this.preloadJS(moduleName);
            }
            
            this.preloadedModules.add(moduleName);
            
        } catch (error) {
            console.error(`Failed to preload ${moduleName}:`, error);
        }
    }
    
    preloadCSS(href) {
        const link = document.createElement('link');
        link.rel = 'preload';
        link.as = 'style';
        link.href = href;
        link.onload = () => {
            // Convert preload to stylesheet
            link.rel = 'stylesheet';
        };
        document.head.appendChild(link);
    }
    
    async preloadJS(src) {
        // Use preload for JavaScript
        const link = document.createElement('link');
        link.rel = 'preload';
        link.as = 'script';
        link.href = src;
        document.head.appendChild(link);
        
        // Or use prefetch for lower priority
        // link.rel = 'prefetch';
    }
    
    // Clean up
    disconnect() {
        this.observers.forEach(observer => observer.disconnect());
        this.observers.clear();
    }
}

Performance Monitoring

javascript

// Performance monitoring utilities
class PerformanceMonitor {
    constructor() {
        this.metrics = new Map();
        this.resourceTimings = [];
        this.longTasks = [];
        
        this.setupObservers();
    }
    
    setupObservers() {
        // Observe Performance Timeline
        if ('PerformanceObserver' in window) {
            // Long tasks observer
            const longTaskObserver = new PerformanceObserver((list) => {
                list.getEntries().forEach(entry => {
                    if (entry.duration > 50) { // Tasks longer than 50ms
                        this.longTasks.push(entry);
                        this.reportLongTask(entry);
                    }
                });
            });
            longTaskObserver.observe({ entryTypes: ['longtask'] });
            
            // Layout shift observer
            const layoutShiftObserver = new PerformanceObserver((list) => {
                list.getEntries().forEach(entry => {
                    if (!entry.hadRecentInput) {
                        this.metrics.set('CLS', 
                            (this.metrics.get('CLS') || 0) + entry.value
                        );
                    }
                });
            });
            layoutShiftObserver.observe({ entryTypes: ['layout-shift'] });
            
            // Largest Contentful Paint observer
            const lcpObserver = new PerformanceObserver((list) => {
                const entries = list.getEntries();
                const lastEntry = entries[entries.length - 1];
                this.metrics.set('LCP', lastEntry.startTime);
            });
            lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
        }
        
        // Monitor resource loading
        this.monitorResourceTiming();
    }
    
    monitorResourceTiming() {
        // Get all resource timing entries
        const resources = performance.getEntriesByType('resource');
        this.resourceTimings = resources.map(resource => ({
            name: resource.name,
            duration: resource.duration,
            transferSize: resource.transferSize,
            initiatorType: resource.initiatorType
        }));
        
        // Log slow resources
        const slowResources = resources.filter(r => r.duration > 1000);
        if (slowResources.length > 0) {
            console.warn('Slow resources detected:', slowResources);
        }
    }
    
    reportLongTask(task) {
        console.warn(`Long task detected: ${task.duration.toFixed(2)}ms`, task);
        
        // Send to analytics
        this.sendToAnalytics('long_task', {
            duration: task.duration,
            startTime: task.startTime,
            attribution: task.attribution
        });
    }
    
    measureFunction(func, name) {
        const start = performance.now();
        const result = func();
        const duration = performance.now() - start;
        
        this.metrics.set(name, duration);
        
        if (duration > 100) {
            console.warn(`Slow function "${name}": ${duration.toFixed(2)}ms`);
        }
        
        return result;
    }
    
    async measureAsyncFunction(func, name) {
        const start = performance.now();
        const result = await func();
        const duration = performance.now() - start;
        
        this.metrics.set(name, duration);
        
        if (duration > 500) {
            console.warn(`Slow async function "${name}": ${duration.toFixed(2)}ms`);
        }
        
        return result;
    }
    
    getMetrics() {
        // Get Core Web Vitals
        const fcp = performance.getEntriesByName('first-contentful-paint')[0];
        const lcp = this.metrics.get('LCP');
        const fid = performance.getEntriesByName('first-input')[0];
        const cls = this.metrics.get('CLS');
        
        return {
            fcp: fcp?.startTime,
            lcp,
            fid: fid?.duration,
            cls,
            longTasks: this.longTasks.length,
            resources: this.resourceTimings.length,
            // Custom metrics
            customMetrics: Object.fromEntries(this.metrics)
        };
    }
    
    sendToAnalytics(event, data) {
        // Send to your analytics service
        if (typeof gtag === 'function') {
            gtag('event', event, data);
        }
        
        // Or use navigator.sendBeacon for better performance
        if (navigator.sendBeacon) {
            const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
            navigator.sendBeacon('/analytics', blob);
        }
    }
    
    // Memory monitoring
    monitorMemory() {
        if ('memory' in performance) {
            setInterval(() => {
                const memory = performance.memory;
                console.log('Memory usage:', {
                    used: Math.round(memory.usedJSHeapSize / 1024 / 1024) + 'MB',
                    total: Math.round(memory.totalJSHeapSize / 1024 / 1024) + 'MB',
                    limit: Math.round(memory.jsHeapSizeLimit / 1024 / 1024) + 'MB'
                });
                
                // Warn if memory usage is high
                if (memory.usedJSHeapSize / memory.totalJSHeapSize > 0.8) {
                    console.warn('High memory usage detected!');
                }
            }, 10000); // Check every 10 seconds
        }
    }
}

// Usage
const perfMonitor = new PerformanceMonitor();

// Measure critical functions
const expensiveCalculation = perfMonitor.measureFunction(() => {
    // Your expensive code here
}, 'expensiveCalculation');

// Get metrics and report
window.addEventListener('load', () => {
    setTimeout(() => {
        const metrics = perfMonitor.getMetrics();
        console.log('Performance metrics:', metrics);
        
        // Send to your analytics
        perfMonitor.sendToAnalytics('performance_metrics', metrics);
    }, 5000); // Wait a bit after load
});

Key Performance Optimization Tips

  1. Minimize DOM Manipulation: Batch changes, use DocumentFragments

  2. Debounce Frequent Events: Input, resize, scroll events

  3. Throttle Animation Frames: Mouse move, scroll handlers

  4. Lazy Load Images: Use Intersection Observer

  5. Split Code: Dynamic imports for non-critical features

  6. Cache Results: Memoize expensive calculations

  7. Use Web Workers: Offload heavy computations

  8. Optimize Loops: Reduce complexity, break early

  9. Avoid Forced Synchronous Layout: Batch read/write operations

  10. Monitor Performance: Use Performance API to identify bottlenecks

Note: Measure before you optimize. Use Chrome DevTools Performance tab to identify actual bottlenecks rather than guessing what might be slow.

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?