javascript

examples

examples.js
/**
 * 21.3 Performance Optimization - Examples
 *
 * Demonstrating performance optimization techniques
 */

// ================================================
// 1. PERFORMANCE MEASUREMENT
// ================================================

/**
 * Performance Timer
 * High-precision timing for performance measurement
 */
class PerformanceTimer {
  constructor() {
    this.marks = new Map();
    this.measures = [];
  }

  // Mark a point in time
  mark(name) {
    this.marks.set(name, performance.now());
    return this;
  }

  // Measure between two marks
  measure(name, startMark, endMark) {
    const start = this.marks.get(startMark);
    const end = this.marks.get(endMark) || performance.now();

    if (start === undefined) {
      throw new Error(`Start mark "${startMark}" not found`);
    }

    const duration = end - start;
    this.measures.push({ name, start, end, duration });

    return duration;
  }

  // Get all measures
  getMeasures() {
    return this.measures;
  }

  // Clear all data
  clear() {
    this.marks.clear();
    this.measures = [];
  }

  // Console report
  report() {
    console.log('Performance Report');
    console.log('='.repeat(50));

    for (const m of this.measures) {
      console.log(`${m.name}: ${m.duration.toFixed(2)}ms`);
    }

    const total = this.measures.reduce((sum, m) => sum + m.duration, 0);
    console.log('-'.repeat(50));
    console.log(`Total: ${total.toFixed(2)}ms`);
  }
}

// Example usage
const timer = new PerformanceTimer();

timer.mark('start');
// Simulate work
for (let i = 0; i < 100000; i++) {
  Math.sqrt(i);
}
timer.mark('afterMath');

// More work
const arr = Array.from({ length: 10000 }, (_, i) => i);
arr.sort(() => Math.random() - 0.5);
timer.mark('afterSort');

console.log(
  'Math operations:',
  timer.measure('Math', 'start', 'afterMath').toFixed(2) + 'ms'
);
console.log(
  'Sort operation:',
  timer.measure('Sort', 'afterMath', 'afterSort').toFixed(2) + 'ms'
);

// ================================================
// 2. MEMOIZATION & CACHING
// ================================================

/**
 * Advanced Memoization with Options
 */
function memoize(fn, options = {}) {
  const {
    maxSize = 100,
    ttl = Infinity,
    keyGenerator = (...args) => JSON.stringify(args),
  } = options;

  const cache = new Map();
  const timestamps = new Map();

  function memoized(...args) {
    const key = keyGenerator(...args);
    const now = Date.now();

    // Check cache hit
    if (cache.has(key)) {
      const timestamp = timestamps.get(key);
      if (now - timestamp < ttl) {
        return cache.get(key);
      }
      // Expired, remove from cache
      cache.delete(key);
      timestamps.delete(key);
    }

    // Compute result
    const result = fn.apply(this, args);

    // LRU eviction if needed
    if (cache.size >= maxSize) {
      const oldestKey = cache.keys().next().value;
      cache.delete(oldestKey);
      timestamps.delete(oldestKey);
    }

    // Store in cache
    cache.set(key, result);
    timestamps.set(key, now);

    return result;
  }

  memoized.clear = () => {
    cache.clear();
    timestamps.clear();
  };

  memoized.size = () => cache.size;

  return memoized;
}

// Example: Expensive computation
function expensiveCalculation(n) {
  // Simulate expensive work
  let result = 0;
  for (let i = 0; i < n * 1000; i++) {
    result += Math.sqrt(i);
  }
  return result;
}

const memoizedCalc = memoize(expensiveCalculation, { maxSize: 50, ttl: 5000 });

console.log('\n' + '='.repeat(50));
console.log('Memoization Example:');

timer.mark('noCache1');
expensiveCalculation(100);
timer.mark('noCache2');
console.log(
  'Without cache:',
  timer.measure('noCache', 'noCache1', 'noCache2').toFixed(2) + 'ms'
);

timer.mark('cache1');
memoizedCalc(100);
timer.mark('cache2');
console.log(
  'First call (cached):',
  timer.measure('cache1st', 'cache1', 'cache2').toFixed(2) + 'ms'
);

timer.mark('cache3');
memoizedCalc(100);
timer.mark('cache4');
console.log(
  'Second call (from cache):',
  timer.measure('cache2nd', 'cache3', 'cache4').toFixed(2) + 'ms'
);

/**
 * LRU Cache Implementation
 */
class LRUCache {
  constructor(capacity) {
    this.capacity = capacity;
    this.cache = new Map();
  }

  get(key) {
    if (!this.cache.has(key)) {
      return undefined;
    }

    // Move to end (most recently used)
    const value = this.cache.get(key);
    this.cache.delete(key);
    this.cache.set(key, value);

    return value;
  }

  put(key, value) {
    // Remove existing to update position
    if (this.cache.has(key)) {
      this.cache.delete(key);
    }

    // Evict oldest if at capacity
    if (this.cache.size >= this.capacity) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }

    this.cache.set(key, value);
  }

  has(key) {
    return this.cache.has(key);
  }

  delete(key) {
    return this.cache.delete(key);
  }

  clear() {
    this.cache.clear();
  }

  get size() {
    return this.cache.size;
  }
}

console.log('\n' + '='.repeat(50));
console.log('LRU Cache Example:');

const lru = new LRUCache(3);
lru.put('a', 1);
lru.put('b', 2);
lru.put('c', 3);
console.log('Cache after a,b,c:', [...lru.cache.keys()]);

lru.get('a'); // Access 'a', making it most recently used
lru.put('d', 4); // 'b' gets evicted as least recently used
console.log('Cache after get(a), put(d):', [...lru.cache.keys()]);

// ================================================
// 3. DEBOUNCE & THROTTLE
// ================================================

/**
 * Debounce Function
 * Delays execution until after wait milliseconds have elapsed
 * since the last call
 */
function debounce(fn, wait, options = {}) {
  const { leading = false, trailing = true, maxWait } = options;

  let timeout = null;
  let lastCallTime = 0;
  let lastInvokeTime = 0;
  let result;

  function invokeFunc(time) {
    lastInvokeTime = time;
    result = fn.apply(this, arguments);
    return result;
  }

  function shouldInvoke(time) {
    const timeSinceLastCall = time - lastCallTime;
    const timeSinceLastInvoke = time - lastInvokeTime;

    return (
      lastCallTime === 0 ||
      timeSinceLastCall >= wait ||
      (maxWait !== undefined && timeSinceLastInvoke >= maxWait)
    );
  }

  function debounced(...args) {
    const time = Date.now();
    const isInvoking = shouldInvoke(time);

    lastCallTime = time;

    if (isInvoking) {
      if (timeout === null && leading) {
        return invokeFunc.apply(this, args);
      }
    }

    if (timeout !== null) {
      clearTimeout(timeout);
    }

    if (trailing) {
      timeout = setTimeout(() => {
        timeout = null;
        if (trailing && lastCallTime !== 0) {
          invokeFunc.apply(this, args);
        }
      }, wait);
    }

    return result;
  }

  debounced.cancel = () => {
    if (timeout !== null) {
      clearTimeout(timeout);
      timeout = null;
    }
    lastCallTime = 0;
    lastInvokeTime = 0;
  };

  debounced.flush = () => {
    if (timeout !== null) {
      clearTimeout(timeout);
      timeout = null;
    }
  };

  return debounced;
}

/**
 * Throttle Function
 * Limits execution to at most once per wait milliseconds
 */
function throttle(fn, wait, options = {}) {
  const { leading = true, trailing = true } = options;

  let timeout = null;
  let lastArgs = null;
  let lastCallTime = 0;

  function invokeFunc() {
    fn.apply(this, lastArgs);
    lastArgs = null;
  }

  function throttled(...args) {
    const now = Date.now();
    const elapsed = now - lastCallTime;

    lastArgs = args;

    if (elapsed >= wait || lastCallTime === 0) {
      lastCallTime = now;

      if (leading) {
        invokeFunc.call(this);
      }

      if (timeout !== null) {
        clearTimeout(timeout);
        timeout = null;
      }

      if (trailing && !leading) {
        timeout = setTimeout(() => {
          invokeFunc.call(this);
          timeout = null;
        }, wait);
      }
    } else if (trailing && timeout === null) {
      timeout = setTimeout(() => {
        lastCallTime = Date.now();
        invokeFunc.call(this);
        timeout = null;
      }, wait - elapsed);
    }
  }

  throttled.cancel = () => {
    if (timeout !== null) {
      clearTimeout(timeout);
      timeout = null;
    }
    lastArgs = null;
    lastCallTime = 0;
  };

  return throttled;
}

console.log('\n' + '='.repeat(50));
console.log('Debounce & Throttle Example:');

let debounceCount = 0;
let throttleCount = 0;

const debouncedFn = debounce(() => debounceCount++, 100);
const throttledFn = throttle(() => throttleCount++, 100);

// Simulate rapid calls
for (let i = 0; i < 10; i++) {
  debouncedFn();
  throttledFn();
}

setTimeout(() => {
  console.log(`Debounce calls: ${debounceCount} (expected: 1)`);
  console.log(`Throttle calls: ${throttleCount} (expected: 1-2)`);
}, 200);

// ================================================
// 4. CHUNKING LONG TASKS
// ================================================

/**
 * Task Chunker
 * Breaks long tasks into smaller chunks to avoid blocking UI
 */
class TaskChunker {
  constructor(options = {}) {
    this.chunkSize = options.chunkSize || 100;
    this.yieldInterval = options.yieldInterval || 16; // ~60fps
  }

  // Process array in chunks with yield to event loop
  async processArray(items, processFn, onProgress) {
    const results = [];
    const total = items.length;

    for (let i = 0; i < total; i += this.chunkSize) {
      const chunk = items.slice(i, Math.min(i + this.chunkSize, total));

      // Process chunk
      for (const item of chunk) {
        results.push(processFn(item));
      }

      // Report progress
      if (onProgress) {
        onProgress((i + chunk.length) / total);
      }

      // Yield to event loop
      await this.yieldToEventLoop();
    }

    return results;
  }

  // Process items using requestIdleCallback (browser) or setTimeout
  async processWhenIdle(items, processFn) {
    const results = [];
    let index = 0;

    return new Promise((resolve) => {
      const processNextChunk = (deadline) => {
        // Process while we have time
        while (index < items.length && this.hasTimeRemaining(deadline)) {
          results.push(processFn(items[index]));
          index++;
        }

        if (index < items.length) {
          // Schedule more work
          this.scheduleWork(processNextChunk);
        } else {
          resolve(results);
        }
      };

      this.scheduleWork(processNextChunk);
    });
  }

  // Yield to event loop
  yieldToEventLoop() {
    return new Promise((resolve) => setTimeout(resolve, 0));
  }

  // Check if we have time remaining (for requestIdleCallback compatibility)
  hasTimeRemaining(deadline) {
    if (deadline && typeof deadline.timeRemaining === 'function') {
      return deadline.timeRemaining() > 0;
    }
    return true; // In Node.js, always return true
  }

  // Schedule work (requestIdleCallback or setTimeout)
  scheduleWork(callback) {
    if (typeof requestIdleCallback === 'function') {
      requestIdleCallback(callback);
    } else {
      setTimeout(
        () => callback({ timeRemaining: () => this.yieldInterval }),
        0
      );
    }
  }
}

console.log('\n' + '='.repeat(50));
console.log('Task Chunking Example:');

const chunker = new TaskChunker({ chunkSize: 1000 });
const largeArray = Array.from({ length: 10000 }, (_, i) => i);

// Heavy processing function
function heavyProcess(n) {
  let result = 0;
  for (let i = 0; i < 100; i++) {
    result += Math.sqrt(n * i);
  }
  return result;
}

(async () => {
  timer.mark('chunkedStart');

  await chunker.processArray(largeArray, heavyProcess, (progress) => {
    if (progress === 1) {
      timer.mark('chunkedEnd');
      console.log(
        'Chunked processing:',
        timer.measure('chunked', 'chunkedStart', 'chunkedEnd').toFixed(2) + 'ms'
      );
    }
  });
})();

// ================================================
// 5. LAZY LOADING
// ================================================

/**
 * Lazy Value
 * Computes value only when first accessed
 */
class Lazy {
  constructor(factory) {
    this.factory = factory;
    this.computed = false;
    this.value = undefined;
  }

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

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

  isComputed() {
    return this.computed;
  }
}

/**
 * Lazy Module Loader
 * Simulates dynamic import behavior
 */
class ModuleLoader {
  constructor() {
    this.modules = new Map();
    this.loading = new Map();
  }

  // Register a module factory
  register(name, factory) {
    this.modules.set(name, factory);
  }

  // Load module on demand
  async load(name) {
    // Return cached module
    if (this.loading.has(name)) {
      const result = this.loading.get(name);
      if (result.loaded) {
        return result.exports;
      }
      return result.promise;
    }

    const factory = this.modules.get(name);
    if (!factory) {
      throw new Error(`Module "${name}" not found`);
    }

    const result = { loaded: false, exports: null };

    result.promise = (async () => {
      result.exports = await factory();
      result.loaded = true;
      return result.exports;
    })();

    this.loading.set(name, result);

    return result.promise;
  }

  // Preload modules
  preload(...names) {
    return Promise.all(names.map((name) => this.load(name)));
  }
}

console.log('\n' + '='.repeat(50));
console.log('Lazy Loading Example:');

const loader = new ModuleLoader();

// Register modules
loader.register('heavy-math', async () => {
  // Simulate loading delay
  await new Promise((r) => setTimeout(r, 100));
  return {
    calculate: (x) => Math.pow(x, 2),
  };
});

loader.register('utils', async () => {
  await new Promise((r) => setTimeout(r, 50));
  return {
    format: (n) => n.toFixed(2),
  };
});

// Load on demand
(async () => {
  timer.mark('loadStart');
  const math = await loader.load('heavy-math');
  timer.mark('loadEnd');

  console.log(
    'Module loaded in:',
    timer.measure('moduleLoad', 'loadStart', 'loadEnd').toFixed(2) + 'ms'
  );
  console.log('Calculate(5):', math.calculate(5));
})();

// ================================================
// 6. OBJECT POOLING
// ================================================

/**
 * Object Pool
 * Reuses objects to avoid garbage collection overhead
 */
class ObjectPool {
  constructor(factory, options = {}) {
    this.factory = factory;
    this.reset = options.reset || (() => {});
    this.maxSize = options.maxSize || 100;
    this.pool = [];
  }

  // Acquire an object from pool
  acquire() {
    if (this.pool.length > 0) {
      return this.pool.pop();
    }
    return this.factory();
  }

  // Release object back to pool
  release(obj) {
    if (this.pool.length < this.maxSize) {
      this.reset(obj);
      this.pool.push(obj);
    }
  }

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

  get size() {
    return this.pool.length;
  }
}

console.log('\n' + '='.repeat(50));
console.log('Object Pool Example:');

// Pool for particle objects
const particlePool = new ObjectPool(
  () => ({ x: 0, y: 0, vx: 0, vy: 0, life: 0 }),
  {
    reset: (p) => {
      p.x = 0;
      p.y = 0;
      p.vx = 0;
      p.vy = 0;
      p.life = 0;
    },
    maxSize: 1000,
  }
);

// Simulate particle system
timer.mark('poolStart');

for (let i = 0; i < 10000; i++) {
  const particle = particlePool.acquire();
  particle.x = Math.random() * 100;
  particle.y = Math.random() * 100;
  particle.life = 1;

  // Simulate particle life
  particle.life = 0;

  particlePool.release(particle);
}

timer.mark('poolEnd');
console.log(
  'Pool operations:',
  timer.measure('pool', 'poolStart', 'poolEnd').toFixed(2) + 'ms'
);
console.log('Pool size after use:', particlePool.size);

// ================================================
// 7. VIRTUAL LIST
// ================================================

/**
 * Virtual List
 * Renders only visible items for large datasets
 */
class VirtualList {
  constructor(options) {
    this.items = options.items || [];
    this.itemHeight = options.itemHeight || 40;
    this.containerHeight = options.containerHeight || 400;
    this.overscan = options.overscan || 3; // Extra items to render
    this.scrollTop = 0;
  }

  // Get visible items and their positions
  getVisibleItems() {
    const startIndex = Math.floor(this.scrollTop / this.itemHeight);
    const endIndex = Math.min(
      startIndex +
        Math.ceil(this.containerHeight / this.itemHeight) +
        this.overscan,
      this.items.length
    );

    const startWithOverscan = Math.max(0, startIndex - this.overscan);

    return {
      items: this.items.slice(startWithOverscan, endIndex).map((item, i) => ({
        item,
        index: startWithOverscan + i,
        top: (startWithOverscan + i) * this.itemHeight,
      })),
      totalHeight: this.items.length * this.itemHeight,
      startIndex: startWithOverscan,
      endIndex,
    };
  }

  // Update scroll position
  setScrollTop(scrollTop) {
    this.scrollTop = scrollTop;
  }

  // Update items
  setItems(items) {
    this.items = items;
  }

  // Scroll to specific item
  scrollToIndex(index) {
    this.scrollTop = index * this.itemHeight;
  }
}

console.log('\n' + '='.repeat(50));
console.log('Virtual List Example:');

const largeDataset = Array.from({ length: 100000 }, (_, i) => ({
  id: i,
  name: `Item ${i}`,
}));

const virtualList = new VirtualList({
  items: largeDataset,
  itemHeight: 40,
  containerHeight: 400,
});

// Simulate scroll
virtualList.setScrollTop(0);
console.log(
  'At top:',
  virtualList.getVisibleItems().items.length,
  'items rendered'
);

virtualList.setScrollTop(10000);
console.log(
  'After scroll:',
  virtualList.getVisibleItems().items.length,
  'items rendered'
);
console.log(
  'Rendering',
  virtualList.getVisibleItems().items.length,
  'of',
  largeDataset.length,
  'items'
);

// ================================================
// 8. BATCH PROCESSOR
// ================================================

/**
 * Batch Processor
 * Batches operations together for efficiency
 */
class BatchProcessor {
  constructor(processFn, options = {}) {
    this.processFn = processFn;
    this.batchSize = options.batchSize || 50;
    this.delay = options.delay || 16;
    this.pending = [];
    this.timeout = null;
  }

  // Add item to batch
  add(item) {
    return new Promise((resolve, reject) => {
      this.pending.push({ item, resolve, reject });

      if (this.pending.length >= this.batchSize) {
        this.flush();
      } else if (!this.timeout) {
        this.timeout = setTimeout(() => this.flush(), this.delay);
      }
    });
  }

  // Process all pending items
  async flush() {
    if (this.timeout) {
      clearTimeout(this.timeout);
      this.timeout = null;
    }

    if (this.pending.length === 0) return;

    const batch = this.pending;
    this.pending = [];

    try {
      const items = batch.map((b) => b.item);
      const results = await this.processFn(items);

      batch.forEach((b, i) => b.resolve(results[i]));
    } catch (error) {
      batch.forEach((b) => b.reject(error));
    }
  }
}

console.log('\n' + '='.repeat(50));
console.log('Batch Processor Example:');

// Simulate batch API call
const apiBatcher = new BatchProcessor(
  async (items) => {
    console.log(`Processing batch of ${items.length} items`);
    // Simulate API delay
    await new Promise((r) => setTimeout(r, 50));
    return items.map((item) => ({ ...item, processed: true }));
  },
  { batchSize: 5, delay: 100 }
);

// Add items (will be batched)
(async () => {
  const promises = [];
  for (let i = 0; i < 12; i++) {
    promises.push(apiBatcher.add({ id: i }));
  }

  const results = await Promise.all(promises);
  console.log(`Processed ${results.length} items in batches`);
})();

// ================================================
// 9. REQUEST DEDUPLICATION
// ================================================

/**
 * Request Deduplicator
 * Prevents duplicate concurrent requests
 */
class RequestDeduplicator {
  constructor() {
    this.pending = new Map();
  }

  // Deduplicated request
  async request(key, requestFn) {
    // Return existing promise if request in progress
    if (this.pending.has(key)) {
      return this.pending.get(key);
    }

    // Create new request
    const promise = requestFn().finally(() => {
      this.pending.delete(key);
    });

    this.pending.set(key, promise);

    return promise;
  }

  // Check if request is pending
  isPending(key) {
    return this.pending.has(key);
  }

  // Cancel pending request
  cancel(key) {
    this.pending.delete(key);
  }
}

console.log('\n' + '='.repeat(50));
console.log('Request Deduplication Example:');

const deduplicator = new RequestDeduplicator();

async function fetchUser(id) {
  console.log(`Fetching user ${id}...`);
  await new Promise((r) => setTimeout(r, 100));
  return { id, name: `User ${id}` };
}

(async () => {
  // Multiple calls with same key = single request
  const [user1, user2, user3] = await Promise.all([
    deduplicator.request('user-1', () => fetchUser(1)),
    deduplicator.request('user-1', () => fetchUser(1)),
    deduplicator.request('user-1', () => fetchUser(1)),
  ]);

  console.log('All three calls returned same result');
  console.log('Results:', user1.id === user2.id && user2.id === user3.id);
})();

// ================================================
// EXPORTS
// ================================================

module.exports = {
  PerformanceTimer,
  memoize,
  LRUCache,
  debounce,
  throttle,
  TaskChunker,
  Lazy,
  ModuleLoader,
  ObjectPool,
  VirtualList,
  BatchProcessor,
  RequestDeduplicator,
};
Examples - JavaScript Tutorial | DeepML