javascript
examples
examples.js⚡javascript
/**
* 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,
};