Advanced JavaScriptFeatured

JavaScript Performance Optimization: Complete Guide

Master JavaScript performance optimization. Learn profiling, memory management, rendering optimization, and best practices for fast applications.

By JavaScriptDoc Team
performanceoptimizationjavascriptprofilingmemory

JavaScript Performance Optimization: Complete Guide

Performance optimization is crucial for creating fast, responsive JavaScript applications. This guide covers profiling, optimization techniques, and best practices.

Performance Profiling

Using Performance API

// Basic performance measurement
const startTime = performance.now();

// Code to measure
for (let i = 0; i < 1000000; i++) {
  // Some operation
}

const endTime = performance.now();
console.log(`Operation took ${endTime - startTime} milliseconds`);

// Using performance marks and measures
performance.mark('myFunction-start');

// Function to profile
function complexOperation() {
  const results = [];
  for (let i = 0; i < 10000; i++) {
    results.push(Math.sqrt(i) * Math.random());
  }
  return results;
}

complexOperation();

performance.mark('myFunction-end');
performance.measure('myFunction', 'myFunction-start', 'myFunction-end');

// Get measurements
const measures = performance.getEntriesByType('measure');
console.log(measures);

// Performance Observer
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(`${entry.name}: ${entry.duration}ms`);
  }
});

observer.observe({ entryTypes: ['measure', 'navigation'] });

Memory Profiling

// Memory usage monitoring
class MemoryMonitor {
  static getMemoryInfo() {
    if (performance.memory) {
      return {
        usedJSHeapSize:
          (performance.memory.usedJSHeapSize / 1048576).toFixed(2) + ' MB',
        totalJSHeapSize:
          (performance.memory.totalJSHeapSize / 1048576).toFixed(2) + ' MB',
        jsHeapSizeLimit:
          (performance.memory.jsHeapSizeLimit / 1048576).toFixed(2) + ' MB',
      };
    }
    return null;
  }

  static trackMemoryUsage(operation, name = 'Operation') {
    const before = performance.memory ? performance.memory.usedJSHeapSize : 0;
    const startTime = performance.now();

    const result = operation();

    const after = performance.memory ? performance.memory.usedJSHeapSize : 0;
    const endTime = performance.now();

    console.log(`${name}:`, {
      duration: `${(endTime - startTime).toFixed(2)}ms`,
      memoryUsed: `${((after - before) / 1048576).toFixed(2)} MB`,
      totalMemory: this.getMemoryInfo(),
    });

    return result;
  }
}

// Usage
MemoryMonitor.trackMemoryUsage(() => {
  const largeArray = new Array(1000000).fill('data');
  return largeArray;
}, 'Large Array Creation');

Memory Management

Identifying Memory Leaks

// Common memory leak patterns

// 1. Forgotten timers
class LeakyComponent {
  constructor() {
    this.data = new Array(1000000).fill('data');
    // Memory leak: timer holds reference
    this.timer = setInterval(() => {
      console.log(this.data.length);
    }, 1000);
  }

  // Fix: Clear timer
  destroy() {
    clearInterval(this.timer);
  }
}

// 2. Event listener leaks
class EventLeaker {
  constructor() {
    this.handleClick = this.handleClick.bind(this);
    // Memory leak: listener not removed
    document.addEventListener('click', this.handleClick);
  }

  handleClick() {
    console.log('Clicked');
  }

  // Fix: Remove listener
  cleanup() {
    document.removeEventListener('click', this.handleClick);
  }
}

// 3. Closure leaks
function createLeak() {
  const largeData = new Array(1000000).fill('data');

  // Memory leak: closure keeps largeData alive
  return function () {
    console.log(largeData.length);
  };
}

// Fix: Release reference when done
function createNonLeak() {
  let largeData = new Array(1000000).fill('data');

  return {
    use() {
      console.log(largeData ? largeData.length : 0);
    },
    cleanup() {
      largeData = null; // Release reference
    },
  };
}

// 4. DOM reference leaks
class DOMLeaker {
  constructor() {
    this.elements = [];

    // Memory leak: keeping references to removed DOM elements
    for (let i = 0; i < 100; i++) {
      const div = document.createElement('div');
      document.body.appendChild(div);
      this.elements.push(div);
      div.remove(); // Removed from DOM but still referenced
    }
  }

  // Fix: Clear references
  cleanup() {
    this.elements = [];
  }
}

Memory-Efficient Patterns

// Object pooling
class ObjectPool {
  constructor(createFn, resetFn, maxSize = 100) {
    this.createFn = createFn;
    this.resetFn = resetFn;
    this.pool = [];
    this.maxSize = maxSize;
  }

  acquire() {
    if (this.pool.length > 0) {
      return this.pool.pop();
    }
    return this.createFn();
  }

  release(obj) {
    if (this.pool.length < this.maxSize) {
      this.resetFn(obj);
      this.pool.push(obj);
    }
  }

  clear() {
    this.pool = [];
  }
}

// Usage example
const particlePool = new ObjectPool(
  () => ({ x: 0, y: 0, velocity: { x: 0, y: 0 }, active: false }),
  (particle) => {
    particle.x = 0;
    particle.y = 0;
    particle.velocity.x = 0;
    particle.velocity.y = 0;
    particle.active = false;
  }
);

// Lazy loading and virtualization
class VirtualList {
  constructor(items, itemHeight, containerHeight) {
    this.items = items;
    this.itemHeight = itemHeight;
    this.containerHeight = containerHeight;
    this.visibleCount = Math.ceil(containerHeight / itemHeight);
    this.startIndex = 0;
  }

  getVisibleItems() {
    return this.items.slice(
      this.startIndex,
      this.startIndex + this.visibleCount + 1 // Buffer
    );
  }

  scrollTo(scrollTop) {
    this.startIndex = Math.floor(scrollTop / this.itemHeight);
    return this.getVisibleItems();
  }

  getTotalHeight() {
    return this.items.length * this.itemHeight;
  }
}

// WeakMap for metadata
class MetadataManager {
  constructor() {
    this.metadata = new WeakMap();
  }

  setMetadata(object, data) {
    this.metadata.set(object, data);
  }

  getMetadata(object) {
    return this.metadata.get(object);
  }

  // No need to manually clean up - garbage collected automatically
}

JavaScript Optimization

Loop Optimization

// Loop optimization techniques

// 1. Cache array length
// Bad
for (let i = 0; i < array.length; i++) {
  // array.length calculated each iteration
}

// Good
const len = array.length;
for (let i = 0; i < len; i++) {
  // Length cached
}

// 2. Minimize work in loops
// Bad
for (let i = 0; i < items.length; i++) {
  const result = expensiveOperation(items[i]);
  const formatted = formatResult(result);
  const validated = validateResult(formatted);
  processResult(validated);
}

// Good - batch operations
const results = items.map(expensiveOperation);
const formatted = results.map(formatResult);
const validated = formatted.map(validateResult);
validated.forEach(processResult);

// 3. Use appropriate loop constructs
class LoopBenchmark {
  static benchmark(array, iterations = 1000) {
    const results = {};

    // for loop
    const forStart = performance.now();
    for (let iter = 0; iter < iterations; iter++) {
      let sum = 0;
      for (let i = 0; i < array.length; i++) {
        sum += array[i];
      }
    }
    results.for = performance.now() - forStart;

    // for...of
    const forOfStart = performance.now();
    for (let iter = 0; iter < iterations; iter++) {
      let sum = 0;
      for (const value of array) {
        sum += value;
      }
    }
    results.forOf = performance.now() - forOfStart;

    // forEach
    const forEachStart = performance.now();
    for (let iter = 0; iter < iterations; iter++) {
      let sum = 0;
      array.forEach((value) => {
        sum += value;
      });
    }
    results.forEach = performance.now() - forEachStart;

    // reduce
    const reduceStart = performance.now();
    for (let iter = 0; iter < iterations; iter++) {
      const sum = array.reduce((acc, value) => acc + value, 0);
    }
    results.reduce = performance.now() - reduceStart;

    return results;
  }
}

// 4. Loop unrolling for small fixed iterations
function processPixels(pixels) {
  // Instead of:
  // for (let i = 0; i < pixels.length; i += 4) {
  //   processRGBA(pixels[i], pixels[i+1], pixels[i+2], pixels[i+3]);
  // }

  // Unrolled version (faster for modern JIT compilers)
  let i = 0;
  const len = pixels.length;

  // Process 4 pixels at a time
  for (; i < len - 15; i += 16) {
    processRGBA(pixels[i], pixels[i + 1], pixels[i + 2], pixels[i + 3]);
    processRGBA(pixels[i + 4], pixels[i + 5], pixels[i + 6], pixels[i + 7]);
    processRGBA(pixels[i + 8], pixels[i + 9], pixels[i + 10], pixels[i + 11]);
    processRGBA(pixels[i + 12], pixels[i + 13], pixels[i + 14], pixels[i + 15]);
  }

  // Handle remaining pixels
  for (; i < len; i += 4) {
    processRGBA(pixels[i], pixels[i + 1], pixels[i + 2], pixels[i + 3]);
  }
}

Function Optimization

// Function optimization patterns

// 1. Memoization
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: Expensive calculation
const fibonacci = memoize(function (n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
});

// 2. Debouncing and throttling
function debounce(fn, delay) {
  let timeoutId;

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

function throttle(fn, limit) {
  let inThrottle;

  return function (...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => (inThrottle = false), limit);
    }
  };
}

// 3. Lazy evaluation
class LazyValue {
  constructor(factory) {
    this.factory = factory;
    this.hasValue = false;
    this.value = undefined;
  }

  get() {
    if (!this.hasValue) {
      this.value = this.factory();
      this.hasValue = true;
    }
    return this.value;
  }

  reset() {
    this.hasValue = false;
    this.value = undefined;
  }
}

// 4. Function inlining hints
class MathOptimized {
  // Small functions that should be inlined
  static add(a, b) {
    return a + b;
  }
  static multiply(a, b) {
    return a * b;
  }

  // Complex calculation using inlined functions
  static calculate(x, y, z) {
    // JIT compiler will likely inline these calls
    const sum = this.add(x, y);
    const product = this.multiply(sum, z);
    return product;
  }
}

Data Structure Optimization

// Choosing the right data structure

// 1. Map vs Object for frequent lookups
class LookupBenchmark {
  static compare(keys, iterations = 10000) {
    // Object lookup
    const obj = {};
    keys.forEach((key) => (obj[key] = Math.random()));

    const objStart = performance.now();
    for (let i = 0; i < iterations; i++) {
      keys.forEach((key) => obj[key]);
    }
    const objTime = performance.now() - objStart;

    // Map lookup
    const map = new Map();
    keys.forEach((key) => map.set(key, Math.random()));

    const mapStart = performance.now();
    for (let i = 0; i < iterations; i++) {
      keys.forEach((key) => map.get(key));
    }
    const mapTime = performance.now() - mapStart;

    return { object: objTime, map: mapTime };
  }
}

// 2. Set for unique values
class UniqueFilter {
  // Slow: O(n²)
  static slowUnique(array) {
    const unique = [];
    for (const item of array) {
      if (!unique.includes(item)) {
        unique.push(item);
      }
    }
    return unique;
  }

  // Fast: O(n)
  static fastUnique(array) {
    return [...new Set(array)];
  }
}

// 3. TypedArrays for numeric data
class NumericProcessor {
  static processWithArray(size) {
    const arr = new Array(size);
    for (let i = 0; i < size; i++) {
      arr[i] = Math.random() * 100;
    }

    let sum = 0;
    for (let i = 0; i < size; i++) {
      sum += arr[i];
    }
    return sum;
  }

  static processWithTypedArray(size) {
    const arr = new Float32Array(size);
    for (let i = 0; i < size; i++) {
      arr[i] = Math.random() * 100;
    }

    let sum = 0;
    for (let i = 0; i < size; i++) {
      sum += arr[i];
    }
    return sum;
  }
}

// 4. Efficient string building
class StringBuilder {
  constructor() {
    this.parts = [];
  }

  append(str) {
    this.parts.push(str);
    return this;
  }

  toString() {
    return this.parts.join('');
  }
}

// Better than string concatenation in loops
const sb = new StringBuilder();
for (let i = 0; i < 10000; i++) {
  sb.append(`Item ${i}, `);
}
const result = sb.toString();

DOM Performance

Batch DOM Updates

// DOM manipulation optimization

// 1. Batch DOM updates
class DOMBatcher {
  constructor() {
    this.pending = [];
    this.scheduled = false;
  }

  update(fn) {
    this.pending.push(fn);

    if (!this.scheduled) {
      this.scheduled = true;
      requestAnimationFrame(() => this.flush());
    }
  }

  flush() {
    const updates = this.pending.splice(0);
    updates.forEach((fn) => fn());
    this.scheduled = false;
  }
}

// Usage
const batcher = new DOMBatcher();

// Instead of immediate DOM updates
for (let i = 0; i < 1000; i++) {
  batcher.update(() => {
    const div = document.createElement('div');
    div.textContent = `Item ${i}`;
    document.body.appendChild(div);
  });
}

// 2. Document fragment for bulk inserts
function createManyElements(count) {
  const fragment = document.createDocumentFragment();

  for (let i = 0; i < count; i++) {
    const div = document.createElement('div');
    div.className = 'item';
    div.textContent = `Item ${i}`;
    fragment.appendChild(div);
  }

  // Single DOM update
  document.getElementById('container').appendChild(fragment);
}

// 3. Virtual DOM concept
class VirtualNode {
  constructor(type, props, children) {
    this.type = type;
    this.props = props || {};
    this.children = children || [];
  }

  render() {
    const element = document.createElement(this.type);

    // Set properties
    Object.entries(this.props).forEach(([key, value]) => {
      if (key === 'className') {
        element.className = value;
      } else if (key.startsWith('on')) {
        const event = key.slice(2).toLowerCase();
        element.addEventListener(event, value);
      } else {
        element.setAttribute(key, value);
      }
    });

    // Render children
    this.children.forEach((child) => {
      if (typeof child === 'string') {
        element.appendChild(document.createTextNode(child));
      } else {
        element.appendChild(child.render());
      }
    });

    return element;
  }
}

// 4. Efficient style updates
class StyleManager {
  static batchStyleUpdate(elements, styles) {
    // Use CSS classes when possible
    const className = 'batch-style-' + Date.now();
    const styleSheet = document.createElement('style');

    const cssRules = Object.entries(styles)
      .map(([prop, value]) => `${prop}: ${value}`)
      .join('; ');

    styleSheet.textContent = `.${className} { ${cssRules} }`;
    document.head.appendChild(styleSheet);

    elements.forEach((el) => el.classList.add(className));

    return () => {
      styleSheet.remove();
      elements.forEach((el) => el.classList.remove(className));
    };
  }
}

Event Delegation

// Event delegation for performance

class EventDelegator {
  constructor(root) {
    this.root = root;
    this.handlers = new Map();
    this.setupDelegation();
  }

  setupDelegation() {
    ['click', 'mouseenter', 'mouseleave', 'focus', 'blur'].forEach(
      (eventType) => {
        this.root.addEventListener(
          eventType,
          (event) => {
            this.handleEvent(eventType, event);
          },
          true
        );
      }
    );
  }

  on(eventType, selector, handler) {
    if (!this.handlers.has(eventType)) {
      this.handlers.set(eventType, new Map());
    }

    this.handlers.get(eventType).set(selector, handler);
  }

  off(eventType, selector) {
    if (this.handlers.has(eventType)) {
      this.handlers.get(eventType).delete(selector);
    }
  }

  handleEvent(eventType, event) {
    const handlers = this.handlers.get(eventType);
    if (!handlers) return;

    handlers.forEach((handler, selector) => {
      const target = event.target.closest(selector);
      if (target && this.root.contains(target)) {
        handler.call(target, event);
      }
    });
  }
}

// Usage
const delegator = new EventDelegator(document.body);

// Handle clicks on all buttons
delegator.on('click', 'button', function (event) {
  console.log('Button clicked:', this.textContent);
});

// Handle hover on list items
delegator.on('mouseenter', 'li', function (event) {
  this.classList.add('hover');
});

delegator.on('mouseleave', 'li', function (event) {
  this.classList.remove('hover');
});

Rendering Performance

RequestAnimationFrame

// Smooth animations with requestAnimationFrame

class AnimationController {
  constructor() {
    this.animations = new Map();
    this.running = false;
  }

  add(id, animationFn) {
    this.animations.set(id, animationFn);

    if (!this.running) {
      this.running = true;
      this.tick();
    }
  }

  remove(id) {
    this.animations.delete(id);

    if (this.animations.size === 0) {
      this.running = false;
    }
  }

  tick(timestamp = 0) {
    if (!this.running) return;

    this.animations.forEach((animationFn, id) => {
      try {
        if (animationFn(timestamp) === false) {
          this.remove(id);
        }
      } catch (error) {
        console.error(`Animation ${id} error:`, error);
        this.remove(id);
      }
    });

    requestAnimationFrame((ts) => this.tick(ts));
  }
}

// Smooth scrolling implementation
class SmoothScroller {
  static scrollTo(target, duration = 1000) {
    const start = window.pageYOffset;
    const distance = target - start;
    const startTime = performance.now();

    function animation(currentTime) {
      const elapsed = currentTime - startTime;
      const progress = Math.min(elapsed / duration, 1);

      // Easing function
      const easeInOutQuad =
        progress < 0.5
          ? 2 * progress * progress
          : 1 - Math.pow(-2 * progress + 2, 2) / 2;

      window.scrollTo(0, start + distance * easeInOutQuad);

      if (progress < 1) {
        requestAnimationFrame(animation);
      }
    }

    requestAnimationFrame(animation);
  }
}

// FPS monitor
class FPSMonitor {
  constructor(callback) {
    this.callback = callback;
    this.frames = 0;
    this.lastTime = performance.now();
    this.fps = 0;
  }

  start() {
    this.measure();
  }

  measure() {
    this.frames++;

    const currentTime = performance.now();
    const delta = currentTime - this.lastTime;

    if (delta >= 1000) {
      this.fps = Math.round((this.frames * 1000) / delta);
      this.frames = 0;
      this.lastTime = currentTime;
      this.callback(this.fps);
    }

    requestAnimationFrame(() => this.measure());
  }
}

Layout Thrashing Prevention

// Preventing layout thrashing

class LayoutBatchReader {
  constructor() {
    this.reads = [];
    this.writes = [];
    this.scheduled = false;
  }

  read(fn) {
    this.reads.push(fn);
    this.scheduleFlush();
  }

  write(fn) {
    this.writes.push(fn);
    this.scheduleFlush();
  }

  scheduleFlush() {
    if (!this.scheduled) {
      this.scheduled = true;
      requestAnimationFrame(() => this.flush());
    }
  }

  flush() {
    // Perform all reads first
    const readResults = this.reads.map((fn) => fn());

    // Then perform all writes
    this.writes.forEach((fn, i) => fn(readResults[i]));

    this.reads = [];
    this.writes = [];
    this.scheduled = false;
  }
}

// Usage to prevent layout thrashing
const layoutBatcher = new LayoutBatchReader();

// Bad: Causes layout thrashing
function badResize(elements) {
  elements.forEach((el) => {
    el.style.height = el.offsetWidth + 'px'; // Read then write in loop
  });
}

// Good: Batched reads and writes
function goodResize(elements) {
  elements.forEach((el) => {
    layoutBatcher.read(() => el.offsetWidth);
    layoutBatcher.write((width) => {
      el.style.height = width + 'px';
    });
  });
}

// CSS containment for performance
class PerformantComponent {
  constructor(container) {
    this.container = container;
    this.applyContainment();
  }

  applyContainment() {
    // Limit scope of layout calculations
    this.container.style.contain = 'layout style paint';

    // Will-change for animations
    if (this.willAnimate) {
      this.container.style.willChange = 'transform';
    }
  }

  startAnimation() {
    this.willAnimate = true;
    this.container.style.willChange = 'transform';
  }

  endAnimation() {
    this.willAnimate = false;
    this.container.style.willChange = 'auto';
  }
}

Web Workers

Offloading Heavy Computations

// Main thread
class WorkerPool {
  constructor(workerScript, poolSize = navigator.hardwareConcurrency || 4) {
    this.workers = [];
    this.queue = [];
    this.busy = new Set();

    for (let i = 0; i < poolSize; i++) {
      const worker = new Worker(workerScript);
      worker.id = i;
      this.workers.push(worker);
    }
  }

  execute(data) {
    return new Promise((resolve, reject) => {
      const task = { data, resolve, reject };

      const availableWorker = this.getAvailableWorker();
      if (availableWorker) {
        this.runTask(availableWorker, task);
      } else {
        this.queue.push(task);
      }
    });
  }

  getAvailableWorker() {
    return this.workers.find((worker) => !this.busy.has(worker.id));
  }

  runTask(worker, task) {
    this.busy.add(worker.id);

    const handleMessage = (event) => {
      worker.removeEventListener('message', handleMessage);
      worker.removeEventListener('error', handleError);

      this.busy.delete(worker.id);
      task.resolve(event.data);

      // Process next task in queue
      if (this.queue.length > 0) {
        const nextTask = this.queue.shift();
        this.runTask(worker, nextTask);
      }
    };

    const handleError = (error) => {
      worker.removeEventListener('message', handleMessage);
      worker.removeEventListener('error', handleError);

      this.busy.delete(worker.id);
      task.reject(error);
    };

    worker.addEventListener('message', handleMessage);
    worker.addEventListener('error', handleError);
    worker.postMessage(task.data);
  }

  terminate() {
    this.workers.forEach((worker) => worker.terminate());
    this.workers = [];
    this.queue = [];
    this.busy.clear();
  }
}

// worker.js
self.addEventListener('message', (event) => {
  const { type, data } = event.data;

  switch (type) {
    case 'PROCESS_DATA':
      const result = processLargeDataset(data);
      self.postMessage({ type: 'RESULT', data: result });
      break;

    case 'CALCULATE':
      const calculation = performComplexCalculation(data);
      self.postMessage({ type: 'RESULT', data: calculation });
      break;
  }
});

function processLargeDataset(data) {
  // Heavy computation
  return data.map((item) => {
    // Complex processing
    return transformItem(item);
  });
}

// Using SharedArrayBuffer for shared memory
if (typeof SharedArrayBuffer !== 'undefined') {
  class SharedMemoryProcessor {
    constructor(size) {
      this.buffer = new SharedArrayBuffer(size);
      this.view = new Float32Array(this.buffer);
    }

    process(workerCount) {
      const chunkSize = Math.ceil(this.view.length / workerCount);
      const workers = [];

      for (let i = 0; i < workerCount; i++) {
        const worker = new Worker('shared-worker.js');
        const start = i * chunkSize;
        const end = Math.min(start + chunkSize, this.view.length);

        worker.postMessage({
          buffer: this.buffer,
          start,
          end,
        });

        workers.push(worker);
      }

      return Promise.all(
        workers.map(
          (worker) =>
            new Promise((resolve) => {
              worker.onmessage = () => resolve();
            })
        )
      );
    }
  }
}

Caching Strategies

// Advanced caching patterns

class CacheManager {
  constructor() {
    this.caches = new Map();
  }

  createCache(name, options = {}) {
    const cache = new Cache(options);
    this.caches.set(name, cache);
    return cache;
  }

  getCache(name) {
    return this.caches.get(name);
  }
}

class Cache {
  constructor(options = {}) {
    this.store = new Map();
    this.maxSize = options.maxSize || 100;
    this.ttl = options.ttl || null;
    this.strategy = options.strategy || 'LRU'; // LRU, LFU, FIFO
    this.stats = { hits: 0, misses: 0 };

    if (this.strategy === 'LRU') {
      this.accessOrder = new Map();
    } else if (this.strategy === 'LFU') {
      this.frequency = new Map();
    }
  }

  get(key) {
    if (this.store.has(key)) {
      const entry = this.store.get(key);

      // Check TTL
      if (this.ttl && Date.now() - entry.timestamp > this.ttl) {
        this.delete(key);
        this.stats.misses++;
        return undefined;
      }

      this.stats.hits++;
      this.updateAccessInfo(key);
      return entry.value;
    }

    this.stats.misses++;
    return undefined;
  }

  set(key, value) {
    // Evict if necessary
    if (this.store.size >= this.maxSize && !this.store.has(key)) {
      this.evict();
    }

    this.store.set(key, {
      value,
      timestamp: Date.now(),
    });

    this.updateAccessInfo(key);
  }

  updateAccessInfo(key) {
    if (this.strategy === 'LRU') {
      // Move to end (most recently used)
      this.accessOrder.delete(key);
      this.accessOrder.set(key, Date.now());
    } else if (this.strategy === 'LFU') {
      // Increment frequency
      const freq = this.frequency.get(key) || 0;
      this.frequency.set(key, freq + 1);
    }
  }

  evict() {
    let keyToEvict;

    switch (this.strategy) {
      case 'LRU':
        // Evict least recently used
        keyToEvict = this.accessOrder.keys().next().value;
        break;

      case 'LFU':
        // Evict least frequently used
        let minFreq = Infinity;
        for (const [key, freq] of this.frequency) {
          if (freq < minFreq) {
            minFreq = freq;
            keyToEvict = key;
          }
        }
        break;

      case 'FIFO':
        // Evict first inserted
        keyToEvict = this.store.keys().next().value;
        break;
    }

    if (keyToEvict) {
      this.delete(keyToEvict);
    }
  }

  delete(key) {
    this.store.delete(key);
    this.accessOrder?.delete(key);
    this.frequency?.delete(key);
  }

  clear() {
    this.store.clear();
    this.accessOrder?.clear();
    this.frequency?.clear();
    this.stats = { hits: 0, misses: 0 };
  }

  getStats() {
    const total = this.stats.hits + this.stats.misses;
    return {
      ...this.stats,
      hitRate: total > 0 ? this.stats.hits / total : 0,
      size: this.store.size,
    };
  }
}

// Specialized caches
class ComputeCache {
  constructor() {
    this.cache = new Cache({ strategy: 'LRU', maxSize: 1000 });
  }

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

      let result = this.cache.get(key);
      if (result !== undefined) {
        return result;
      }

      result = fn(...args);
      this.cache.set(key, result);
      return result;
    };
  }
}

Best Practices

  1. Profile before optimizing

    // Measure first
    performance.mark('optimization-start');
    // ... code ...
    performance.mark('optimization-end');
    performance.measure(
      'optimization',
      'optimization-start',
      'optimization-end'
    );
    
  2. Use appropriate data structures

    // Use Set for unique values
    const unique = new Set(array);
    
    // Use Map for key-value pairs with frequent access
    const lookup = new Map(pairs);
    
  3. Avoid premature optimization

    // Focus on readable code first
    // Optimize only proven bottlenecks
    
  4. Monitor performance in production

    // Use Performance Observer API
    // Track real user metrics
    // Set up alerts for regressions
    

Conclusion

JavaScript performance optimization involves:

  • Profiling to identify bottlenecks
  • Memory management to prevent leaks
  • Algorithm optimization for efficiency
  • DOM optimization for smooth rendering
  • Caching to avoid redundant work
  • Async patterns to prevent blocking

Key takeaways:

  • Always measure before optimizing
  • Focus on the critical path
  • Use appropriate data structures
  • Batch DOM operations
  • Leverage browser APIs
  • Consider Web Workers for heavy tasks

Master performance optimization to build fast, responsive JavaScript applications!