JavaScript Performance

JavaScript Performance Optimization: Speed Up Your Applications

Master JavaScript performance optimization techniques. Learn profiling, memory management, code optimization, bundling strategies, and performance monitoring.

By JavaScript Document Team
performanceoptimizationprofilingmemorybundlingmonitoring

Performance optimization is crucial for creating fast, responsive JavaScript applications. This comprehensive guide covers profiling techniques, memory management, code optimization strategies, and performance monitoring to help you build lightning-fast applications.

Performance Profiling and Measurement

Understanding where performance bottlenecks occur is the first step in optimization.

Performance Measurement Tools

// Performance measurement utilities
class PerformanceProfiler {
  constructor() {
    this.measurements = new Map();
    this.markers = new Map();
  }

  // Mark the start of a performance measurement
  mark(name) {
    const markName = `${name}-start`;
    performance.mark(markName);
    this.markers.set(name, Date.now());
    return this;
  }

  // Measure from mark to now
  measure(name) {
    const startMark = `${name}-start`;
    const endMark = `${name}-end`;

    performance.mark(endMark);

    try {
      performance.measure(name, startMark, endMark);
      const measure = performance.getEntriesByName(name, 'measure')[0];

      this.measurements.set(name, {
        duration: measure.duration,
        startTime: measure.startTime,
        endTime: measure.startTime + measure.duration,
      });

      // Clean up marks
      performance.clearMarks(startMark);
      performance.clearMarks(endMark);
      performance.clearMeasures(name);

      return measure.duration;
    } catch (error) {
      console.warn(`Failed to measure ${name}:`, error);
      return null;
    }
  }

  // Time a function execution
  async timeFunction(fn, name = 'function') {
    this.mark(name);

    try {
      const result = await fn();
      const duration = this.measure(name);

      return {
        result,
        duration,
        name,
      };
    } catch (error) {
      this.measure(name);
      throw error;
    }
  }

  // Time multiple iterations of a function
  async benchmark(fn, iterations = 1000, name = 'benchmark') {
    const results = [];

    for (let i = 0; i < iterations; i++) {
      const timing = await this.timeFunction(fn, `${name}-${i}`);
      results.push(timing.duration);
    }

    const sorted = results.sort((a, b) => a - b);
    const sum = results.reduce((acc, val) => acc + val, 0);

    return {
      iterations,
      min: sorted[0],
      max: sorted[sorted.length - 1],
      mean: sum / iterations,
      median: sorted[Math.floor(sorted.length / 2)],
      p95: sorted[Math.floor(sorted.length * 0.95)],
      p99: sorted[Math.floor(sorted.length * 0.99)],
      results,
    };
  }

  // Memory usage measurement
  measureMemory() {
    if ('memory' in performance) {
      return {
        usedJSHeapSize: performance.memory.usedJSHeapSize,
        totalJSHeapSize: performance.memory.totalJSHeapSize,
        jsHeapSizeLimit: performance.memory.jsHeapSizeLimit,
        usedPercentage:
          (performance.memory.usedJSHeapSize /
            performance.memory.jsHeapSizeLimit) *
          100,
      };
    }

    return null;
  }

  // Resource timing analysis
  analyzeResourceTiming() {
    const resources = performance.getEntriesByType('resource');
    const analysis = {
      totalResources: resources.length,
      totalSize: 0,
      totalDuration: 0,
      byType: {},
      slowest: [],
      largest: [],
    };

    resources.forEach((resource) => {
      const type = this.getResourceType(resource.name);
      const duration = resource.responseEnd - resource.startTime;
      const size = resource.transferSize || 0;

      if (!analysis.byType[type]) {
        analysis.byType[type] = {
          count: 0,
          totalSize: 0,
          totalDuration: 0,
          averageDuration: 0,
        };
      }

      analysis.byType[type].count++;
      analysis.byType[type].totalSize += size;
      analysis.byType[type].totalDuration += duration;
      analysis.byType[type].averageDuration =
        analysis.byType[type].totalDuration / analysis.byType[type].count;

      analysis.totalSize += size;
      analysis.totalDuration += duration;

      analysis.slowest.push({ name: resource.name, duration, type });
      analysis.largest.push({ name: resource.name, size, type });
    });

    // Sort for top lists
    analysis.slowest.sort((a, b) => b.duration - a.duration).splice(10);
    analysis.largest.sort((a, b) => b.size - a.size).splice(10);

    return analysis;
  }

  getResourceType(url) {
    if (url.match(/\.(js|mjs)(\?|$)/)) return 'javascript';
    if (url.match(/\.(css)(\?|$)/)) return 'css';
    if (url.match(/\.(png|jpg|jpeg|gif|svg|webp)(\?|$)/)) return 'image';
    if (url.match(/\.(woff|woff2|ttf|eot)(\?|$)/)) return 'font';
    if (url.match(/\.(mp4|webm|ogg|mp3|wav)(\?|$)/)) return 'media';
    return 'other';
  }

  // Generate performance report
  generateReport() {
    return {
      measurements: Object.fromEntries(this.measurements),
      memory: this.measureMemory(),
      resources: this.analyzeResourceTiming(),
      timestamp: new Date().toISOString(),
    };
  }

  // Clear all measurements
  clear() {
    this.measurements.clear();
    this.markers.clear();
    performance.clearMarks();
    performance.clearMeasures();
  }
}

// Usage examples
const profiler = new PerformanceProfiler();

// Basic timing
profiler.mark('data-processing');
// ... do some work
const duration = profiler.measure('data-processing');
console.log(`Processing took ${duration}ms`);

// Function timing
async function expensiveOperation() {
  return new Promise((resolve) => setTimeout(resolve, 100));
}

const result = await profiler.timeFunction(expensiveOperation, 'expensive-op');
console.log(`Operation completed in ${result.duration}ms`);

// Benchmarking
const benchmark = await profiler.benchmark(
  () => {
    return Array.from({ length: 1000 }, (_, i) => i * 2);
  },
  100,
  'array-creation'
);

console.log('Benchmark results:', benchmark);

Core Web Vitals Monitoring

// Core Web Vitals and performance metrics monitoring
class WebVitalsMonitor {
  constructor() {
    this.metrics = new Map();
    this.observers = new Map();
    this.thresholds = {
      LCP: { good: 2500, poor: 4000 },
      FID: { good: 100, poor: 300 },
      CLS: { good: 0.1, poor: 0.25 },
      FCP: { good: 1800, poor: 3000 },
      TTFB: { good: 800, poor: 1800 },
    };

    this.init();
  }

  init() {
    this.observeLCP();
    this.observeFID();
    this.observeCLS();
    this.observeFCP();
    this.observeTTFB();
    this.observeNavigationTiming();
  }

  // Largest Contentful Paint
  observeLCP() {
    if ('PerformanceObserver' in window) {
      const observer = new PerformanceObserver((list) => {
        const entries = list.getEntries();
        const lastEntry = entries[entries.length - 1];

        this.recordMetric('LCP', {
          value: lastEntry.startTime,
          element: lastEntry.element,
          timestamp: Date.now(),
          rating: this.getRating('LCP', lastEntry.startTime),
        });
      });

      observer.observe({ entryTypes: ['largest-contentful-paint'] });
      this.observers.set('LCP', observer);
    }
  }

  // First Input Delay
  observeFID() {
    if ('PerformanceObserver' in window) {
      const observer = new PerformanceObserver((list) => {
        const entries = list.getEntries();
        entries.forEach((entry) => {
          this.recordMetric('FID', {
            value: entry.processingStart - entry.startTime,
            eventType: entry.name,
            timestamp: Date.now(),
            rating: this.getRating(
              'FID',
              entry.processingStart - entry.startTime
            ),
          });
        });
      });

      observer.observe({ entryTypes: ['first-input'] });
      this.observers.set('FID', observer);
    }
  }

  // Cumulative Layout Shift
  observeCLS() {
    if ('PerformanceObserver' in window) {
      let clsValue = 0;
      let sessionValue = 0;
      let sessionEntries = [];

      const observer = new PerformanceObserver((list) => {
        const entries = list.getEntries();

        entries.forEach((entry) => {
          if (!entry.hadRecentInput) {
            const firstSessionEntry = sessionEntries[0];
            const lastSessionEntry = sessionEntries[sessionEntries.length - 1];

            if (
              sessionValue &&
              entry.startTime - lastSessionEntry.startTime < 1000 &&
              entry.startTime - firstSessionEntry.startTime < 5000
            ) {
              sessionValue += entry.value;
              sessionEntries.push(entry);
            } else {
              sessionValue = entry.value;
              sessionEntries = [entry];
            }

            if (sessionValue > clsValue) {
              clsValue = sessionValue;

              this.recordMetric('CLS', {
                value: clsValue,
                entries: sessionEntries.map((e) => ({
                  value: e.value,
                  sources: e.sources,
                })),
                timestamp: Date.now(),
                rating: this.getRating('CLS', clsValue),
              });
            }
          }
        });
      });

      observer.observe({ entryTypes: ['layout-shift'] });
      this.observers.set('CLS', observer);
    }
  }

  // First Contentful Paint
  observeFCP() {
    if ('PerformanceObserver' in window) {
      const observer = new PerformanceObserver((list) => {
        const entries = list.getEntries();
        const fcpEntry = entries.find(
          (entry) => entry.name === 'first-contentful-paint'
        );

        if (fcpEntry) {
          this.recordMetric('FCP', {
            value: fcpEntry.startTime,
            timestamp: Date.now(),
            rating: this.getRating('FCP', fcpEntry.startTime),
          });
        }
      });

      observer.observe({ entryTypes: ['paint'] });
      this.observers.set('FCP', observer);
    }
  }

  // Time to First Byte
  observeTTFB() {
    if ('PerformanceObserver' in window) {
      const observer = new PerformanceObserver((list) => {
        const entries = list.getEntries();
        entries.forEach((entry) => {
          if (entry.entryType === 'navigation') {
            const ttfb = entry.responseStart - entry.requestStart;

            this.recordMetric('TTFB', {
              value: ttfb,
              timestamp: Date.now(),
              rating: this.getRating('TTFB', ttfb),
            });
          }
        });
      });

      observer.observe({ entryTypes: ['navigation'] });
      this.observers.set('TTFB', observer);
    }
  }

  // Navigation timing
  observeNavigationTiming() {
    window.addEventListener('load', () => {
      const navigation = performance.getEntriesByType('navigation')[0];

      if (navigation) {
        this.recordMetric('Navigation', {
          domContentLoaded:
            navigation.domContentLoadedEventEnd -
            navigation.domContentLoadedEventStart,
          loadComplete: navigation.loadEventEnd - navigation.loadEventStart,
          domInteractive: navigation.domInteractive - navigation.fetchStart,
          domComplete: navigation.domComplete - navigation.fetchStart,
          timestamp: Date.now(),
        });
      }
    });
  }

  recordMetric(name, data) {
    if (!this.metrics.has(name)) {
      this.metrics.set(name, []);
    }

    this.metrics.get(name).push(data);

    // Dispatch custom event
    window.dispatchEvent(
      new CustomEvent('webvital', {
        detail: { name, data },
      })
    );
  }

  getRating(metric, value) {
    const thresholds = this.thresholds[metric];
    if (!thresholds) return 'unknown';

    if (value <= thresholds.good) return 'good';
    if (value <= thresholds.poor) return 'needs-improvement';
    return 'poor';
  }

  getMetrics() {
    const result = {};

    this.metrics.forEach((values, name) => {
      const latest = values[values.length - 1];
      result[name] = {
        current: latest,
        history: values,
        count: values.length,
      };
    });

    return result;
  }

  // Get performance score
  getPerformanceScore() {
    const metrics = this.getMetrics();
    const scores = {};
    let totalScore = 0;
    let metricCount = 0;

    Object.entries(metrics).forEach(([name, data]) => {
      if (this.thresholds[name] && data.current) {
        const rating = data.current.rating;
        let score = 0;

        if (rating === 'good') score = 100;
        else if (rating === 'needs-improvement') score = 50;
        else score = 0;

        scores[name] = score;
        totalScore += score;
        metricCount++;
      }
    });

    return {
      overall: metricCount > 0 ? Math.round(totalScore / metricCount) : 0,
      individual: scores,
    };
  }

  // Disconnect all observers
  disconnect() {
    this.observers.forEach((observer) => {
      observer.disconnect();
    });
    this.observers.clear();
  }
}

// Usage
const vitalsMonitor = new WebVitalsMonitor();

// Listen for metric updates
window.addEventListener('webvital', (event) => {
  console.log('Web Vital recorded:', event.detail);
});

// Get current metrics
setTimeout(() => {
  const metrics = vitalsMonitor.getMetrics();
  const score = vitalsMonitor.getPerformanceScore();
  console.log('Current metrics:', metrics);
  console.log('Performance score:', score);
}, 5000);

Memory Management and Optimization

Memory Leak Detection and Prevention

// Memory management utilities
class MemoryManager {
  constructor() {
    this.references = new WeakMap();
    this.eventListeners = new Map();
    this.intervals = new Set();
    this.timeouts = new Set();
    this.observers = new Set();
  }

  // Weak reference storage for objects
  trackObject(obj, metadata = {}) {
    this.references.set(obj, {
      ...metadata,
      createdAt: Date.now(),
      trackedAt: Date.now(),
    });
    return obj;
  }

  // Event listener management
  addEventListener(element, event, handler, options) {
    const listener = { element, event, handler, options };
    this.eventListeners.set(handler, listener);

    element.addEventListener(event, handler, options);

    return () => this.removeEventListener(handler);
  }

  removeEventListener(handler) {
    const listener = this.eventListeners.get(handler);
    if (listener) {
      listener.element.removeEventListener(
        listener.event,
        listener.handler,
        listener.options
      );
      this.eventListeners.delete(handler);
    }
  }

  // Timer management
  setTimeout(callback, delay, ...args) {
    const id = setTimeout(() => {
      this.timeouts.delete(id);
      callback(...args);
    }, delay);

    this.timeouts.add(id);
    return id;
  }

  setInterval(callback, interval, ...args) {
    const id = setInterval(callback, interval, ...args);
    this.intervals.add(id);
    return id;
  }

  clearTimeout(id) {
    clearTimeout(id);
    this.timeouts.delete(id);
  }

  clearInterval(id) {
    clearInterval(id);
    this.intervals.delete(id);
  }

  // Observer management
  createObserver(observerClass, callback, options = {}) {
    const observer = new observerClass(callback);
    observer.observe(document.body, options);
    this.observers.add(observer);
    return observer;
  }

  // Cleanup all managed resources
  cleanup() {
    // Remove event listeners
    this.eventListeners.forEach((listener, handler) => {
      this.removeEventListener(handler);
    });

    // Clear timers
    this.intervals.forEach((id) => clearInterval(id));
    this.timeouts.forEach((id) => clearTimeout(id));

    // Disconnect observers
    this.observers.forEach((observer) => {
      if (observer.disconnect) observer.disconnect();
    });

    this.eventListeners.clear();
    this.intervals.clear();
    this.timeouts.clear();
    this.observers.clear();
  }

  // Memory usage estimation
  estimateObjectSize(obj, seen = new WeakSet()) {
    if (obj === null || typeof obj !== 'object') {
      return this.getPrimitiveSize(obj);
    }

    if (seen.has(obj)) {
      return 0; // Circular reference
    }

    seen.add(obj);

    let size = 0;

    if (Array.isArray(obj)) {
      size += 24; // Array overhead
      for (const item of obj) {
        size += 8; // Pointer size
        size += this.estimateObjectSize(item, seen);
      }
    } else if (obj instanceof Date) {
      size += 24;
    } else if (obj instanceof RegExp) {
      size += 24 + obj.source.length * 2;
    } else {
      size += 24; // Object overhead

      for (const [key, value] of Object.entries(obj)) {
        size += key.length * 2; // String key
        size += 8; // Property overhead
        size += this.estimateObjectSize(value, seen);
      }
    }

    return size;
  }

  getPrimitiveSize(value) {
    switch (typeof value) {
      case 'string':
        return value.length * 2; // UTF-16
      case 'number':
        return 8;
      case 'boolean':
        return 4;
      case 'undefined':
      case 'symbol':
        return 8;
      default:
        return 8;
    }
  }

  // Memory leak detection
  detectMemoryLeaks() {
    const report = {
      eventListeners: this.eventListeners.size,
      intervals: this.intervals.size,
      timeouts: this.timeouts.size,
      observers: this.observers.size,
      detachedNodes: this.findDetachedNodes(),
      largeDOMTrees: this.findLargeDOMTrees(),
    };

    return report;
  }

  findDetachedNodes() {
    // This is a simplified detection - in practice, you'd use browser dev tools
    const allElements = document.querySelectorAll('*');
    const detached = [];

    allElements.forEach((el) => {
      if (
        !document.body.contains(el) &&
        el !== document.body &&
        el !== document.documentElement
      ) {
        detached.push({
          tagName: el.tagName,
          id: el.id,
          className: el.className,
        });
      }
    });

    return detached;
  }

  findLargeDOMTrees(threshold = 1000) {
    const large = [];

    const countDescendants = (element) => {
      return element.querySelectorAll('*').length;
    };

    document.querySelectorAll('*').forEach((el) => {
      const count = countDescendants(el);
      if (count > threshold) {
        large.push({
          element: el.tagName,
          id: el.id,
          className: el.className,
          descendants: count,
        });
      }
    });

    return large.sort((a, b) => b.descendants - a.descendants).slice(0, 10);
  }
}

// Object pool for reducing garbage collection
class ObjectPool {
  constructor(createFn, resetFn, initialSize = 10) {
    this.createFn = createFn;
    this.resetFn = resetFn;
    this.pool = [];
    this.allocated = new Set();

    // Pre-allocate objects
    for (let i = 0; i < initialSize; i++) {
      this.pool.push(this.createFn());
    }
  }

  acquire() {
    let obj;

    if (this.pool.length > 0) {
      obj = this.pool.pop();
    } else {
      obj = this.createFn();
    }

    this.allocated.add(obj);
    return obj;
  }

  release(obj) {
    if (this.allocated.has(obj)) {
      this.allocated.delete(obj);

      if (this.resetFn) {
        this.resetFn(obj);
      }

      this.pool.push(obj);
    }
  }

  releaseAll() {
    this.allocated.forEach((obj) => this.release(obj));
  }

  getStats() {
    return {
      poolSize: this.pool.length,
      allocated: this.allocated.size,
      total: this.pool.length + this.allocated.size,
    };
  }
}

// Usage examples
const memoryManager = new MemoryManager();

// Managed event listeners
const cleanup = memoryManager.addEventListener(
  document.getElementById('button'),
  'click',
  () => console.log('clicked')
);

// Object pool example
const vectorPool = new ObjectPool(
  () => ({ x: 0, y: 0, z: 0 }),
  (vec) => {
    vec.x = 0;
    vec.y = 0;
    vec.z = 0;
  }
);

const vector = vectorPool.acquire();
vector.x = 10;
vector.y = 20;
// ... use vector
vectorPool.release(vector);

// Memory leak detection
const leakReport = memoryManager.detectMemoryLeaks();
console.log('Memory leak report:', leakReport);

// Cleanup on page unload
window.addEventListener('beforeunload', () => {
  memoryManager.cleanup();
  vectorPool.releaseAll();
});

Code Optimization Techniques

Algorithm and Data Structure Optimizations

// Optimized data structures and algorithms
class OptimizedDataStructures {
  // Fast lookup table using Map
  static createLookupTable(array, keyFn) {
    const table = new Map();

    for (let i = 0; i < array.length; i++) {
      const item = array[i];
      const key = keyFn(item);

      if (table.has(key)) {
        // Handle collisions with arrays
        const existing = table.get(key);
        if (Array.isArray(existing)) {
          existing.push(item);
        } else {
          table.set(key, [existing, item]);
        }
      } else {
        table.set(key, item);
      }
    }

    return table;
  }

  // Optimized array operations
  static fastFilter(array, predicate) {
    const result = [];

    for (let i = 0; i < array.length; i++) {
      if (predicate(array[i], i)) {
        result.push(array[i]);
      }
    }

    return result;
  }

  static fastMap(array, mapper) {
    const result = new Array(array.length);

    for (let i = 0; i < array.length; i++) {
      result[i] = mapper(array[i], i);
    }

    return result;
  }

  static fastReduce(array, reducer, initialValue) {
    let accumulator = initialValue;

    for (let i = 0; i < array.length; i++) {
      accumulator = reducer(accumulator, array[i], i);
    }

    return accumulator;
  }

  // Efficient array deduplication
  static deduplicate(array) {
    return [...new Set(array)];
  }

  static deduplicateBy(array, keyFn) {
    const seen = new Set();
    const result = [];

    for (let i = 0; i < array.length; i++) {
      const item = array[i];
      const key = keyFn(item);

      if (!seen.has(key)) {
        seen.add(key);
        result.push(item);
      }
    }

    return result;
  }

  // Binary search for sorted arrays
  static binarySearch(sortedArray, target, compareFn = (a, b) => a - b) {
    let left = 0;
    let right = sortedArray.length - 1;

    while (left <= right) {
      const mid = Math.floor((left + right) / 2);
      const comparison = compareFn(sortedArray[mid], target);

      if (comparison === 0) return mid;
      if (comparison < 0) left = mid + 1;
      else right = mid - 1;
    }

    return -1;
  }

  // Efficient string operations
  static fastStringConcat(strings) {
    // Use array join for better performance with many strings
    if (strings.length > 10) {
      return strings.join('');
    }

    // Use template literals for fewer strings
    return strings.reduce((acc, str) => acc + str, '');
  }

  // Optimized object operations
  static fastObjectClone(obj) {
    // For simple objects, use spread operator
    if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
      const keys = Object.keys(obj);
      if (keys.length < 10) {
        return { ...obj };
      }
    }

    // For complex objects, use JSON (if no functions/undefined values)
    try {
      return JSON.parse(JSON.stringify(obj));
    } catch {
      // Fallback to manual cloning
      return this.deepClone(obj);
    }
  }

  static deepClone(obj, seen = new WeakMap()) {
    if (obj === null || typeof obj !== 'object') return obj;
    if (seen.has(obj)) return seen.get(obj);

    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (obj instanceof Array) {
      const clone = [];
      seen.set(obj, clone);
      for (let i = 0; i < obj.length; i++) {
        clone[i] = this.deepClone(obj[i], seen);
      }
      return clone;
    }

    const clone = {};
    seen.set(obj, clone);
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        clone[key] = this.deepClone(obj[key], seen);
      }
    }

    return clone;
  }
}

// Function memoization for expensive computations
class Memoizer {
  constructor(options = {}) {
    this.cache = new Map();
    this.maxSize = options.maxSize || 1000;
    this.ttl = options.ttl; // Time to live in milliseconds
    this.accessOrder = []; // For LRU eviction
  }

  memoize(fn, keyGenerator = (...args) => JSON.stringify(args)) {
    return (...args) => {
      const key = keyGenerator(...args);
      const now = Date.now();

      // Check if cached result exists and is valid
      if (this.cache.has(key)) {
        const cached = this.cache.get(key);

        if (!this.ttl || now - cached.timestamp < this.ttl) {
          // Update access order for LRU
          this.updateAccessOrder(key);
          return cached.value;
        } else {
          // Expired cache entry
          this.cache.delete(key);
          this.removeFromAccessOrder(key);
        }
      }

      // Compute new result
      const result = fn(...args);

      // Store in cache
      this.cache.set(key, {
        value: result,
        timestamp: now,
      });

      this.updateAccessOrder(key);

      // Evict if necessary
      if (this.cache.size > this.maxSize) {
        this.evictLRU();
      }

      return result;
    };
  }

  updateAccessOrder(key) {
    this.removeFromAccessOrder(key);
    this.accessOrder.push(key);
  }

  removeFromAccessOrder(key) {
    const index = this.accessOrder.indexOf(key);
    if (index > -1) {
      this.accessOrder.splice(index, 1);
    }
  }

  evictLRU() {
    if (this.accessOrder.length > 0) {
      const lruKey = this.accessOrder.shift();
      this.cache.delete(lruKey);
    }
  }

  clear() {
    this.cache.clear();
    this.accessOrder = [];
  }

  getStats() {
    return {
      cacheSize: this.cache.size,
      maxSize: this.maxSize,
      hitRate: this.hitCount / (this.hitCount + this.missCount) || 0,
    };
  }
}

// Debouncing and throttling for performance
class RateLimiter {
  static debounce(fn, delay = 300) {
    let timeoutId;

    return function (...args) {
      clearTimeout(timeoutId);

      timeoutId = setTimeout(() => {
        fn.apply(this, args);
      }, delay);
    };
  }

  static throttle(fn, limit = 100) {
    let inThrottle;

    return function (...args) {
      if (!inThrottle) {
        fn.apply(this, args);
        inThrottle = true;

        setTimeout(() => {
          inThrottle = false;
        }, limit);
      }
    };
  }

  static rafThrottle(fn) {
    let rafId;
    let lastArgs;

    return function (...args) {
      lastArgs = args;

      if (!rafId) {
        rafId = requestAnimationFrame(() => {
          fn.apply(this, lastArgs);
          rafId = null;
        });
      }
    };
  }

  static idleCallback(fn, options = {}) {
    if ('requestIdleCallback' in window) {
      return requestIdleCallback(fn, options);
    } else {
      // Fallback for browsers without requestIdleCallback
      return setTimeout(fn, 0);
    }
  }
}

// Usage examples
const data = [
  { id: 1, name: 'Alice', category: 'A' },
  { id: 2, name: 'Bob', category: 'B' },
  { id: 3, name: 'Charlie', category: 'A' },
];

// Fast lookup table
const lookupTable = OptimizedDataStructures.createLookupTable(
  data,
  (item) => item.category
);
console.log(lookupTable.get('A')); // Items in category A

// Memoized expensive function
const memoizer = new Memoizer({ maxSize: 100, ttl: 60000 });
const expensiveFunction = (n) => {
  // Simulate expensive computation
  let result = 0;
  for (let i = 0; i < n * 1000; i++) {
    result += Math.sqrt(i);
  }
  return result;
};

const memoizedFunction = memoizer.memoize(expensiveFunction);

console.time('First call');
console.log(memoizedFunction(1000)); // Slow
console.timeEnd('First call');

console.time('Second call');
console.log(memoizedFunction(1000)); // Fast (cached)
console.timeEnd('Second call');

// Debounced search
const debouncedSearch = RateLimiter.debounce((query) => {
  console.log('Searching for:', query);
  // Perform search
}, 300);

Bundle Optimization and Code Splitting

Dynamic Imports and Lazy Loading

// Dynamic import utilities for code splitting
class ModuleLoader {
  constructor() {
    this.cache = new Map();
    this.loading = new Map();
    this.retryAttempts = 3;
    this.retryDelay = 1000;
  }

  // Load module with caching and error handling
  async loadModule(modulePath, options = {}) {
    // Return cached module if available
    if (this.cache.has(modulePath)) {
      return this.cache.get(modulePath);
    }

    // Return existing loading promise if in progress
    if (this.loading.has(modulePath)) {
      return this.loading.get(modulePath);
    }

    // Start loading
    const loadingPromise = this.loadWithRetry(modulePath, options);
    this.loading.set(modulePath, loadingPromise);

    try {
      const module = await loadingPromise;
      this.cache.set(modulePath, module);
      this.loading.delete(modulePath);
      return module;
    } catch (error) {
      this.loading.delete(modulePath);
      throw error;
    }
  }

  async loadWithRetry(modulePath, options, attempt = 1) {
    try {
      const module = await import(modulePath);
      return module;
    } catch (error) {
      if (attempt < this.retryAttempts) {
        await this.delay(this.retryDelay * attempt);
        return this.loadWithRetry(modulePath, options, attempt + 1);
      }
      throw error;
    }
  }

  // Preload modules for better performance
  async preloadModule(modulePath) {
    if (this.cache.has(modulePath) || this.loading.has(modulePath)) {
      return;
    }

    // Use link rel="modulepreload" if available
    if ('HTMLLinkElement' in window && 'relList' in HTMLLinkElement.prototype) {
      const link = document.createElement('link');
      link.rel = 'modulepreload';
      link.href = modulePath;
      document.head.appendChild(link);
    }

    // Also start actual loading
    this.loadModule(modulePath).catch(console.error);
  }

  // Load multiple modules in parallel
  async loadModules(modulePaths) {
    const promises = modulePaths.map((path) => this.loadModule(path));
    return Promise.all(promises);
  }

  // Load modules with priority
  async loadModulesWithPriority(moduleConfigs) {
    const highPriority = [];
    const lowPriority = [];

    moduleConfigs.forEach((config) => {
      if (config.priority === 'high') {
        highPriority.push(this.loadModule(config.path));
      } else {
        lowPriority.push(this.loadModule(config.path));
      }
    });

    // Load high priority first
    const highPriorityResults = await Promise.all(highPriority);

    // Then load low priority
    const lowPriorityResults = await Promise.all(lowPriority);

    return [...highPriorityResults, ...lowPriorityResults];
  }

  delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  // Clear cache
  clearCache(modulePath) {
    if (modulePath) {
      this.cache.delete(modulePath);
    } else {
      this.cache.clear();
    }
  }

  // Get loading statistics
  getStats() {
    return {
      cached: this.cache.size,
      loading: this.loading.size,
      cachedModules: Array.from(this.cache.keys()),
    };
  }
}

// Component lazy loading with intersection observer
class LazyComponentLoader {
  constructor(options = {}) {
    this.threshold = options.threshold || 0.1;
    this.rootMargin = options.rootMargin || '50px';
    this.components = new Map();
    this.moduleLoader = new ModuleLoader();

    this.observer = new IntersectionObserver(
      this.handleIntersection.bind(this),
      {
        threshold: this.threshold,
        rootMargin: this.rootMargin,
      }
    );
  }

  // Register a component for lazy loading
  register(element, componentPath, props = {}) {
    this.components.set(element, {
      path: componentPath,
      props,
      loaded: false,
    });

    this.observer.observe(element);

    return {
      unregister: () => {
        this.unregister(element);
      },
    };
  }

  unregister(element) {
    this.observer.unobserve(element);
    this.components.delete(element);
  }

  async handleIntersection(entries) {
    for (const entry of entries) {
      if (entry.isIntersecting) {
        const element = entry.target;
        const config = this.components.get(element);

        if (config && !config.loaded) {
          config.loaded = true;
          this.observer.unobserve(element);

          try {
            await this.loadComponent(element, config);
          } catch (error) {
            console.error('Failed to load component:', error);
            this.showErrorState(element, error);
          }
        }
      }
    }
  }

  async loadComponent(element, config) {
    // Show loading state
    this.showLoadingState(element);

    try {
      const module = await this.moduleLoader.loadModule(config.path);
      const Component = module.default || module;

      // Initialize component
      if (typeof Component === 'function') {
        const instance = new Component(element, config.props);

        if (instance.render) {
          await instance.render();
        }
      } else if (Component.render) {
        await Component.render(element, config.props);
      }

      this.hideLoadingState(element);
    } catch (error) {
      this.hideLoadingState(element);
      throw error;
    }
  }

  showLoadingState(element) {
    element.classList.add('loading');
    element.innerHTML = '<div class="lazy-loading">Loading...</div>';
  }

  hideLoadingState(element) {
    element.classList.remove('loading');
    const loader = element.querySelector('.lazy-loading');
    if (loader) {
      loader.remove();
    }
  }

  showErrorState(element, error) {
    element.classList.add('error');
    element.innerHTML = `<div class="lazy-error">Failed to load component: ${error.message}</div>`;
  }

  // Preload components that will likely be needed
  async preloadComponents(paths) {
    const promises = paths.map((path) => this.moduleLoader.preloadModule(path));
    return Promise.all(promises);
  }

  disconnect() {
    this.observer.disconnect();
    this.components.clear();
  }
}

// Image lazy loading with progressive enhancement
class LazyImageLoader {
  constructor(options = {}) {
    this.threshold = options.threshold || 0.1;
    this.rootMargin = options.rootMargin || '50px';
    this.quality = options.quality || 'medium';

    this.observer = new IntersectionObserver(
      this.handleIntersection.bind(this),
      {
        threshold: this.threshold,
        rootMargin: this.rootMargin,
      }
    );
  }

  observe(img) {
    this.observer.observe(img);
  }

  unobserve(img) {
    this.observer.unobserve(img);
  }

  async handleIntersection(entries) {
    for (const entry of entries) {
      if (entry.isIntersecting) {
        const img = entry.target;
        this.observer.unobserve(img);

        try {
          await this.loadImage(img);
        } catch (error) {
          console.error('Failed to load image:', error);
          this.showErrorState(img);
        }
      }
    }
  }

  async loadImage(img) {
    const src = img.dataset.src;
    if (!src) return;

    // Show loading state
    img.classList.add('loading');

    // Create new image to preload
    const newImg = new Image();

    return new Promise((resolve, reject) => {
      newImg.onload = () => {
        img.src = src;
        img.classList.remove('loading');
        img.classList.add('loaded');
        resolve();
      };

      newImg.onerror = () => {
        img.classList.remove('loading');
        reject(new Error('Failed to load image'));
      };

      // Support for responsive images
      if (img.dataset.srcset) {
        newImg.srcset = img.dataset.srcset;
      }

      newImg.src = src;
    });
  }

  showErrorState(img) {
    img.classList.add('error');
    img.alt = 'Failed to load image';
  }

  // Batch process images
  observeAll(selector = 'img[data-src]') {
    const images = document.querySelectorAll(selector);
    images.forEach((img) => this.observe(img));
    return images.length;
  }

  disconnect() {
    this.observer.disconnect();
  }
}

// Usage examples
const moduleLoader = new ModuleLoader();
const lazyLoader = new LazyComponentLoader();
const imageLoader = new LazyImageLoader();

// Load a module dynamically
async function loadChart() {
  try {
    const chartModule = await moduleLoader.loadModule('./chart-component.js');
    const chart = new chartModule.Chart({
      type: 'line',
      data: [1, 2, 3, 4, 5],
    });
    chart.render();
  } catch (error) {
    console.error('Failed to load chart:', error);
  }
}

// Register lazy components
document.querySelectorAll('[data-component]').forEach((element) => {
  const componentPath = element.dataset.component;
  lazyLoader.register(element, componentPath);
});

// Setup lazy image loading
imageLoader.observeAll();

// Preload critical modules
moduleLoader.preloadModule('./critical-component.js');

// Cleanup on page unload
window.addEventListener('beforeunload', () => {
  lazyLoader.disconnect();
  imageLoader.disconnect();
});

Conclusion

JavaScript performance optimization is an ongoing process that requires understanding your application's specific bottlenecks and user requirements. Start with measurement and profiling to identify actual performance issues, then apply appropriate optimization techniques. Focus on the most impactful optimizations first: reducing bundle sizes, eliminating unnecessary re-renders, optimizing critical rendering paths, and implementing efficient caching strategies. Remember that premature optimization can lead to complex code without meaningful benefits, so always measure the impact of your optimizations.