JsGuide

Learn JavaScript with practical tutorials and code examples

tutorialadvanced18 min read

JavaScript Performance Optimization - Complete Guide

Learn advanced techniques to optimize JavaScript performance including memory management, DOM optimization, async patterns, and profiling tools.

By JSGuide Team

JavaScript Performance Optimization - Complete Guide

Performance optimization is crucial for creating fast, responsive web applications. This comprehensive guide covers advanced techniques to optimize JavaScript performance, from memory management to asynchronous patterns.

1. Memory Management #

Understanding Memory Leaks #

Memory leaks occur when objects are no longer needed but can't be garbage collected due to unwanted references.

// Common memory leak patterns

// 1. Global variables
function createLeak() {
    // Accidentally creates global variable
    leakedVariable = 'This creates a global variable';
}

// 2. Forgotten timers
function startTimer() {
    const data = new Array(1000000).fill('data');
    
    setInterval(() => {
        // This closure keeps 'data' in memory forever
        console.log('Timer running');
    }, 1000);
}

// 3. DOM references
let elements = [];
function addElement() {
    const div = document.createElement('div');
    document.body.appendChild(div);
    elements.push(div); // Keeps reference even after removal
}

// 4. Event listeners
function attachListeners() {
    const button = document.getElementById('myButton');
    const heavyObject = new Array(1000000).fill('data');
    
    button.addEventListener('click', () => {
        // This closure keeps heavyObject in memory
        console.log('Button clicked');
    });
}

Best Practices for Memory Management #

// Good practices to prevent memory leaks

// 1. Use const and let instead of var
function goodScope() {
    const data = 'This is block-scoped';
    let counter = 0;
    // Variables are automatically cleaned up
}

// 2. Clean up timers and intervals
class Timer {
    constructor() {
        this.intervalId = null;
    }
    
    start() {
        this.intervalId = setInterval(() => {
            console.log('Timer tick');
        }, 1000);
    }
    
    stop() {
        if (this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }
    }
}

// 3. Use WeakMap and WeakSet for object associations
const objectData = new WeakMap();

function associateData(obj, data) {
    objectData.set(obj, data);
    // When obj is garbage collected, the association is automatically removed
}

// 4. Remove event listeners
class Component {
    constructor() {
        this.handleClick = this.handleClick.bind(this);
    }
    
    mount() {
        document.addEventListener('click', this.handleClick);
    }
    
    unmount() {
        document.removeEventListener('click', this.handleClick);
    }
    
    handleClick(event) {
        console.log('Clicked:', event.target);
    }
}

// 5. Use AbortController for fetch requests
class ApiClient {
    constructor() {
        this.controller = new AbortController();
    }
    
    async fetchData(url) {
        try {
            const response = await fetch(url, {
                signal: this.controller.signal
            });
            return await response.json();
        } catch (error) {
            if (error.name === 'AbortError') {
                console.log('Request aborted');
            } else {
                throw error;
            }
        }
    }
    
    cancel() {
        this.controller.abort();
    }
}

2. DOM Optimization #

Minimize DOM Manipulations #

// Inefficient DOM manipulation
function inefficientDOM() {
    const list = document.getElementById('list');
    
    for (let i = 0; i < 1000; i++) {
        const item = document.createElement('li');
        item.textContent = `Item ${i}`;
        list.appendChild(item); // Causes reflow for each item
    }
}

// Efficient DOM manipulation
function efficientDOM() {
    const fragment = document.createDocumentFragment();
    
    for (let i = 0; i < 1000; i++) {
        const item = document.createElement('li');
        item.textContent = `Item ${i}`;
        fragment.appendChild(item);
    }
    
    document.getElementById('list').appendChild(fragment); // Single reflow
}

// Batch DOM operations
function batchDOMOperations() {
    const element = document.getElementById('target');
    
    // Bad: multiple style changes
    element.style.width = '100px';
    element.style.height = '100px';
    element.style.backgroundColor = 'red';
    
    // Good: batch changes
    element.style.cssText = 'width: 100px; height: 100px; background-color: red;';
    
    // Or use CSS classes
    element.className = 'optimized-styles';
}

Virtual Scrolling #

// Virtual scrolling implementation for large lists
class VirtualList {
    constructor(container, itemHeight, totalItems) {
        this.container = container;
        this.itemHeight = itemHeight;
        this.totalItems = totalItems;
        this.visibleItems = Math.ceil(container.clientHeight / itemHeight) + 1;
        this.startIndex = 0;
        
        this.setupScrolling();
        this.render();
    }
    
    setupScrolling() {
        // Create spacers for total height
        this.topSpacer = document.createElement('div');
        this.bottomSpacer = document.createElement('div');
        this.content = document.createElement('div');
        
        this.container.appendChild(this.topSpacer);
        this.container.appendChild(this.content);
        this.container.appendChild(this.bottomSpacer);
        
        this.container.addEventListener('scroll', () => {
            this.handleScroll();
        });
    }
    
    handleScroll() {
        const scrollTop = this.container.scrollTop;
        const newStartIndex = Math.floor(scrollTop / this.itemHeight);
        
        if (newStartIndex !== this.startIndex) {
            this.startIndex = newStartIndex;
            this.render();
        }
    }
    
    render() {
        const endIndex = Math.min(
            this.startIndex + this.visibleItems,
            this.totalItems
        );
        
        // Update spacers
        this.topSpacer.style.height = `${this.startIndex * this.itemHeight}px`;
        this.bottomSpacer.style.height = `${(this.totalItems - endIndex) * this.itemHeight}px`;
        
        // Render visible items
        this.content.innerHTML = '';
        for (let i = this.startIndex; i < endIndex; i++) {
            const item = this.createItem(i);
            this.content.appendChild(item);
        }
    }
    
    createItem(index) {
        const item = document.createElement('div');
        item.style.height = `${this.itemHeight}px`;
        item.textContent = `Item ${index}`;
        return item;
    }
}

3. Efficient Algorithms and Data Structures #

Optimizing Loops #

// Slow array operations
function slowArrayOperations(arr) {
    let result = [];
    
    // Multiple passes through array
    const filtered = arr.filter(item => item > 5);
    const mapped = filtered.map(item => item * 2);
    const reduced = mapped.reduce((sum, item) => sum + item, 0);
    
    return reduced;
}

// Optimized single pass
function optimizedArrayOperations(arr) {
    return arr.reduce((sum, item) => {
        if (item > 5) {
            return sum + (item * 2);
        }
        return sum;
    }, 0);
}

// Efficient object lookups
function efficientLookups() {
    // Use Map for frequent lookups
    const userMap = new Map();
    const users = [
        { id: 1, name: 'Alice' },
        { id: 2, name: 'Bob' },
        { id: 3, name: 'Charlie' }
    ];
    
    // Build lookup map once
    users.forEach(user => {
        userMap.set(user.id, user);
    });
    
    // O(1) lookup instead of O(n) with find()
    function getUser(id) {
        return userMap.get(id);
    }
}

// Memoization for expensive computations
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;
    };
}

// Example usage
const expensiveCalculation = memoize((n) => {
    console.log('Computing for', n);
    return n * n * n;
});

console.log(expensiveCalculation(5)); // Computes
console.log(expensiveCalculation(5)); // Uses cache

Debouncing and Throttling #

// Debounce - delay execution until after wait time
function debounce(func, wait) {
    let timeout;
    
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func(...args);
        };
        
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

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

// Usage examples
const searchInput = document.getElementById('search');
const scrollHandler = throttle(() => {
    console.log('Scroll event');
}, 100);

const searchHandler = debounce((query) => {
    console.log('Searching for:', query);
}, 300);

window.addEventListener('scroll', scrollHandler);
searchInput.addEventListener('input', (e) => {
    searchHandler(e.target.value);
});

4. Asynchronous Optimization #

Efficient Promise Handling #

// Inefficient sequential operations
async function inefficientAsync() {
    const result1 = await fetch('/api/data1');
    const result2 = await fetch('/api/data2');
    const result3 = await fetch('/api/data3');
    
    return [result1, result2, result3];
}

// Efficient concurrent operations
async function efficientAsync() {
    const [result1, result2, result3] = await Promise.all([
        fetch('/api/data1'),
        fetch('/api/data2'),
        fetch('/api/data3')
    ]);
    
    return [result1, result2, result3];
}

// Controlled concurrency
async function controlledConcurrency(urls, maxConcurrent = 3) {
    const results = [];
    
    for (let i = 0; i < urls.length; i += maxConcurrent) {
        const batch = urls.slice(i, i + maxConcurrent);
        const batchResults = await Promise.all(
            batch.map(url => fetch(url))
        );
        results.push(...batchResults);
    }
    
    return results;
}

// Request deduplication
class RequestDeduplicator {
    constructor() {
        this.pendingRequests = new Map();
    }
    
    async fetch(url) {
        if (this.pendingRequests.has(url)) {
            return this.pendingRequests.get(url);
        }
        
        const promise = fetch(url).then(response => {
            this.pendingRequests.delete(url);
            return response;
        });
        
        this.pendingRequests.set(url, promise);
        return promise;
    }
}

Web Workers for Heavy Computations #

// Main thread (main.js)
class WorkerPool {
    constructor(workerScript, poolSize = 4) {
        this.workers = [];
        this.queue = [];
        this.workerIndex = 0;
        
        for (let i = 0; i < poolSize; i++) {
            this.workers.push(new Worker(workerScript));
        }
    }
    
    async execute(data) {
        return new Promise((resolve, reject) => {
            const worker = this.workers[this.workerIndex];
            this.workerIndex = (this.workerIndex + 1) % this.workers.length;
            
            const handleMessage = (event) => {
                worker.removeEventListener('message', handleMessage);
                worker.removeEventListener('error', handleError);
                resolve(event.data);
            };
            
            const handleError = (error) => {
                worker.removeEventListener('message', handleMessage);
                worker.removeEventListener('error', handleError);
                reject(error);
            };
            
            worker.addEventListener('message', handleMessage);
            worker.addEventListener('error', handleError);
            worker.postMessage(data);
        });
    }
    
    terminate() {
        this.workers.forEach(worker => worker.terminate());
    }
}

// Usage
const workerPool = new WorkerPool('heavy-computation-worker.js');

async function processLargeDataset(dataset) {
    try {
        const results = await Promise.all(
            dataset.map(chunk => workerPool.execute(chunk))
        );
        return results;
    } catch (error) {
        console.error('Worker error:', error);
    }
}
// Worker script (heavy-computation-worker.js)
self.addEventListener('message', (event) => {
    const data = event.data;
    
    // Perform heavy computation
    const result = performHeavyComputation(data);
    
    // Send result back to main thread
    self.postMessage(result);
});

function performHeavyComputation(data) {
    // Simulate heavy computation
    let result = 0;
    for (let i = 0; i < data.length; i++) {
        result += Math.sqrt(data[i]) * Math.sin(data[i]);
    }
    return result;
}

5. Code Splitting and Lazy Loading #

Dynamic Imports #

// Lazy load modules
async function loadModule(moduleName) {
    try {
        const module = await import(`./modules/${moduleName}.js`);
        return module.default;
    } catch (error) {
        console.error('Failed to load module:', moduleName, error);
        throw error;
    }
}

// Component lazy loading
class ComponentLoader {
    constructor() {
        this.cache = new Map();
    }
    
    async loadComponent(componentName) {
        if (this.cache.has(componentName)) {
            return this.cache.get(componentName);
        }
        
        const component = await import(`./components/${componentName}.js`);
        this.cache.set(componentName, component);
        return component;
    }
}

// Route-based code splitting
class Router {
    constructor() {
        this.routes = new Map();
    }
    
    addRoute(path, loader) {
        this.routes.set(path, loader);
    }
    
    async navigate(path) {
        const loader = this.routes.get(path);
        if (loader) {
            const module = await loader();
            // Render component
            module.render();
        }
    }
}

// Usage
const router = new Router();
router.addRoute('/dashboard', () => import('./pages/Dashboard.js'));
router.addRoute('/profile', () => import('./pages/Profile.js'));

Image and Resource Lazy Loading #

// Intersection Observer for lazy loading
class LazyLoader {
    constructor() {
        this.observer = new IntersectionObserver(
            (entries) => this.handleIntersection(entries),
            { rootMargin: '50px' }
        );
    }
    
    observe(element) {
        this.observer.observe(element);
    }
    
    handleIntersection(entries) {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                this.loadResource(entry.target);
                this.observer.unobserve(entry.target);
            }
        });
    }
    
    loadResource(element) {
        if (element.dataset.src) {
            element.src = element.dataset.src;
        }
        
        if (element.dataset.module) {
            import(element.dataset.module).then(module => {
                element.textContent = module.content;
            });
        }
    }
}

// Usage
const lazyLoader = new LazyLoader();
document.querySelectorAll('[data-lazy]').forEach(element => {
    lazyLoader.observe(element);
});

6. Performance Monitoring #

Performance Metrics #

// Performance monitoring class
class PerformanceMonitor {
    constructor() {
        this.metrics = new Map();
        this.observers = [];
    }
    
    // Mark performance points
    mark(name) {
        performance.mark(name);
    }
    
    // Measure time between marks
    measure(name, startMark, endMark) {
        performance.measure(name, startMark, endMark);
        const measure = performance.getEntriesByName(name)[0];
        this.metrics.set(name, measure.duration);
        return measure.duration;
    }
    
    // Time function execution
    timeFunction(fn, name) {
        const startTime = performance.now();
        const result = fn();
        const endTime = performance.now();
        const duration = endTime - startTime;
        
        this.metrics.set(name, duration);
        console.log(`${name} took ${duration.toFixed(2)}ms`);
        
        return result;
    }
    
    // Monitor frame rate
    monitorFPS() {
        let lastTime = performance.now();
        let frameCount = 0;
        
        const measureFPS = (currentTime) => {
            frameCount++;
            
            if (currentTime - lastTime >= 1000) {
                const fps = frameCount / ((currentTime - lastTime) / 1000);
                console.log(`FPS: ${fps.toFixed(2)}`);
                
                frameCount = 0;
                lastTime = currentTime;
            }
            
            requestAnimationFrame(measureFPS);
        };
        
        requestAnimationFrame(measureFPS);
    }
    
    // Monitor memory usage
    monitorMemory() {
        if (performance.memory) {
            const memory = performance.memory;
            console.log({
                used: `${(memory.usedJSHeapSize / 1024 / 1024).toFixed(2)} MB`,
                total: `${(memory.totalJSHeapSize / 1024 / 1024).toFixed(2)} MB`,
                limit: `${(memory.jsHeapSizeLimit / 1024 / 1024).toFixed(2)} MB`
            });
        }
    }
    
    // Long task detection
    detectLongTasks() {
        if ('PerformanceObserver' in window) {
            const observer = new PerformanceObserver((list) => {
                list.getEntries().forEach(entry => {
                    if (entry.duration > 50) {
                        console.warn(`Long task detected: ${entry.duration.toFixed(2)}ms`);
                    }
                });
            });
            
            observer.observe({ entryTypes: ['longtask'] });
        }
    }
}

// Usage
const monitor = new PerformanceMonitor();

// Time a function
const result = monitor.timeFunction(() => {
    return heavyComputation();
}, 'heavy-computation');

// Mark and measure
monitor.mark('start-api-call');
fetch('/api/data').then(() => {
    monitor.mark('end-api-call');
    monitor.measure('api-call-duration', 'start-api-call', 'end-api-call');
});

// Start monitoring
monitor.monitorFPS();
monitor.detectLongTasks();

Custom Performance Hooks #

// Performance timing hooks
class PerformanceHooks {
    constructor() {
        this.hooks = new Map();
    }
    
    // Register performance hook
    register(name, callback) {
        if (!this.hooks.has(name)) {
            this.hooks.set(name, []);
        }
        this.hooks.get(name).push(callback);
    }
    
    // Trigger performance measurement
    measure(name, duration, metadata = {}) {
        const hooks = this.hooks.get(name) || [];
        hooks.forEach(callback => {
            callback(duration, metadata);
        });
    }
    
    // Create performance decorator
    createDecorator(name) {
        return (target, propertyKey, descriptor) => {
            const originalMethod = descriptor.value;
            
            descriptor.value = function(...args) {
                const start = performance.now();
                const result = originalMethod.apply(this, args);
                const end = performance.now();
                
                this.measure(name, end - start, {
                    method: propertyKey,
                    args: args.length
                });
                
                return result;
            };
        };
    }
}

// Usage
const perfHooks = new PerformanceHooks();

perfHooks.register('api-call', (duration, metadata) => {
    if (duration > 1000) {
        console.warn(`Slow API call: ${duration.toFixed(2)}ms`);
    }
});

perfHooks.register('render', (duration, metadata) => {
    if (duration > 16) {
        console.warn(`Slow render: ${duration.toFixed(2)}ms`);
    }
});

7. Bundle Optimization #

Tree Shaking #

// Efficient imports to enable tree shaking
// Bad: imports entire library
import _ from 'lodash';

// Good: import only what you need
import { debounce, throttle } from 'lodash';

// Even better: import from specific modules
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';

// Custom utility functions
export const utils = {
    debounce,
    throttle,
    formatDate: (date) => date.toLocaleDateString(),
    formatCurrency: (amount) => `$${amount.toFixed(2)}`
};

Resource Hints #

<!-- Preload critical resources -->
<link rel="preload" href="critical.js" as="script">
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="hero-image.jpg" as="image">

<!-- Prefetch resources for next navigation -->
<link rel="prefetch" href="next-page.js">
<link rel="prefetch" href="next-page.css">

<!-- Preconnect to external domains -->
<link rel="preconnect" href="https://api.example.com">
<link rel="preconnect" href="https://fonts.googleapis.com">

8. Testing Performance #

Benchmarking #

// Benchmark suite
class BenchmarkSuite {
    constructor() {
        this.tests = [];
    }
    
    add(name, fn) {
        this.tests.push({ name, fn });
    }
    
    async run() {
        const results = [];
        
        for (const test of this.tests) {
            const iterations = 1000;
            const times = [];
            
            // Warmup
            for (let i = 0; i < 100; i++) {
                test.fn();
            }
            
            // Measure
            for (let i = 0; i < iterations; i++) {
                const start = performance.now();
                test.fn();
                const end = performance.now();
                times.push(end - start);
            }
            
            const avg = times.reduce((a, b) => a + b) / times.length;
            const min = Math.min(...times);
            const max = Math.max(...times);
            
            results.push({
                name: test.name,
                avg: avg.toFixed(4),
                min: min.toFixed(4),
                max: max.toFixed(4)
            });
        }
        
        console.table(results);
        return results;
    }
}

// Usage
const suite = new BenchmarkSuite();

suite.add('Array.map', () => {
    const arr = Array.from({ length: 1000 }, (_, i) => i);
    return arr.map(x => x * 2);
});

suite.add('For loop', () => {
    const arr = Array.from({ length: 1000 }, (_, i) => i);
    const result = [];
    for (let i = 0; i < arr.length; i++) {
        result.push(arr[i] * 2);
    }
    return result;
});

suite.run();

Summary #

JavaScript performance optimization involves:

  1. Memory Management - Prevent leaks and optimize garbage collection
  2. DOM Optimization - Minimize reflows and batch operations
  3. Efficient Algorithms - Use appropriate data structures and algorithms
  4. Async Optimization - Handle promises efficiently and use Web Workers
  5. Code Splitting - Load code on demand
  6. Performance Monitoring - Measure and track performance metrics
  7. Bundle Optimization - Minimize bundle size and enable tree shaking
  8. Testing - Benchmark and profile your code

Regular profiling and monitoring help identify bottlenecks and ensure your applications remain fast and responsive.

Next Steps #

Related Tutorials

Tutorialintermediate

Asynchronous JavaScript - Promises, Async/Await, and Fetch

Master asynchronous programming in JavaScript with promises, async/await, and the Fetch API. Learn to handle API calls, timers, and concurrent operations.

#javascript #async #promises +2 more
Read Tutorial →
TutorialBeginner
4 min min read

Best Way to Learn JavaScript: Common Questions Answered

Find answers to frequently asked questions about the best way to learn JavaScript, including timelines, resources, and effective study methods.

#javascript #learning #faq +2 more
Read Tutorial →

Last updated: Jan 11, 2025

Tutorialintermediate

DOM Manipulation with JavaScript - Complete Guide

Master DOM manipulation in JavaScript. Learn to select elements, modify content, handle events, and create dynamic web pages with practical examples.

#javascript #dom #manipulation +2 more
Read Tutorial →
Tutorialintermediate

ES6+ Features Every JavaScript Developer Should Know

Master modern JavaScript with ES6+ features including arrow functions, destructuring, modules, async/await, and more essential syntax improvements.

#javascript #es6 #modern +2 more
Read Tutorial →