Performance Optimization and Memory Management
60 minPerformance optimization is crucial for creating responsive web applications that provide a good user experience.
JavaScript performance can be improved through code optimization, memory management, and efficient algorithms.
Understanding the JavaScript engine's internals helps write more performant code.
Tools like Chrome DevTools, Lighthouse, and performance APIs help identify and fix performance bottlenecks.
Exercise
Create a comprehensive performance monitoring and optimization system that demonstrates various optimization techniques.
// Performance Optimization and Monitoring System
class PerformanceMonitor {
constructor() {
this.metrics = new Map();
this.observers = new Map();
this.startTime = performance.now();
}
startTimer(name) {
const timer = {
name,
startTime: performance.now(),
endTime: null,
duration: null
};
this.metrics.set(name, timer);
return timer;
}
endTimer(name) {
const timer = this.metrics.get(name);
if (timer) {
timer.endTime = performance.now();
timer.duration = timer.endTime - timer.startTime;
this.emit('timerEnded', timer);
}
}
measureFunction(fn, name, ...args) {
const timer = this.startTimer(name);
try {
const result = fn(...args);
this.endTimer(name);
return result;
} catch (error) {
this.endTimer(name);
throw error;
}
}
async measureAsyncFunction(fn, name, ...args) {
const timer = this.startTimer(name);
try {
const result = await fn(...args);
this.endTimer(name);
return result;
} catch (error) {
this.endTimer(name);
throw error;
}
}
getMetrics() {
return Array.from(this.metrics.values());
}
getAverageDuration() {
const timers = this.getMetrics().filter(t => t.duration !== null);
if (timers.length === 0) return 0;
const totalDuration = timers.reduce((sum, timer) => sum + timer.duration, 0);
return totalDuration / timers.length;
}
on(event, callback) {
if (!this.observers.has(event)) {
this.observers.set(event, []);
}
this.observers.get(event).push(callback);
}
emit(event, data) {
const callbacks = this.observers.get(event);
if (callbacks) {
callbacks.forEach(callback => callback(data));
}
}
}
// Memory Management Utilities
class MemoryManager {
constructor() {
this.weakRefs = new Set();
this.cleanupCallbacks = new Set();
}
createWeakReference(obj, cleanupCallback) {
const weakRef = new WeakRef(obj);
this.weakRefs.add(weakRef);
if (cleanupCallback) {
this.cleanupCallbacks.add(cleanupCallback);
}
return weakRef;
}
cleanup() {
const beforeSize = this.weakRefs.size;
// Remove dead weak references
for (const weakRef of this.weakRefs) {
if (weakRef.deref() === undefined) {
this.weakRefs.delete(weakRef);
}
}
const afterSize = this.weakRefs.size;
const cleaned = beforeSize - afterSize;
if (cleaned > 0) {
console.log(`๐งน Cleaned up ${cleaned} dead references`);
}
return cleaned;
}
getMemoryUsage() {
if ('memory' in performance) {
return {
usedJSHeapSize: performance.memory.usedJSHeapSize,
totalJSHeapSize: performance.memory.totalJSHeapSize,
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit
};
}
return null;
}
}
// Performance Optimization Techniques
class PerformanceOptimizer {
constructor() {
this.monitor = new PerformanceMonitor();
this.memoryManager = new MemoryManager();
}
// Debouncing
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Throttling
throttle(func, limit) {
let inThrottle;
return function executedFunction(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Memoization
memoize(func) {
const cache = new Map();
return function executedFunction(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = func.apply(this, args);
cache.set(key, result);
return result;
};
}
// Lazy Loading
createLazyLoader(loaderFunction) {
let loaded = false;
let data = null;
return {
get() {
if (!loaded) {
data = loaderFunction();
loaded = true;
}
return data;
},
isLoaded() {
return loaded;
},
reset() {
loaded = false;
data = null;
}
};
}
// Virtual Scrolling Helper
createVirtualScroller(itemHeight, containerHeight, totalItems) {
const visibleItems = Math.ceil(containerHeight / itemHeight);
const totalHeight = totalItems * itemHeight;
return {
getVisibleRange(scrollTop) {
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(startIndex + visibleItems, totalItems);
return { startIndex, endIndex };
},
getItemStyle(index) {
return {
position: 'absolute',
top: `${index * itemHeight}px`,
height: `${itemHeight}px`,
width: '100%'
};
},
getContainerStyle() {
return {
height: `${totalHeight}px`,
position: 'relative'
};
}
};
}
}
// Performance Testing Suite
class PerformanceTestSuite {
constructor() {
this.optimizer = new PerformanceOptimizer();
this.monitor = this.optimizer.monitor;
}
// Test different array operations
testArrayOperations() {
const sizes = [1000, 10000, 100000];
const results = {};
sizes.forEach(size => {
const array = Array.from({ length: size }, (_, i) => i);
// Test for loop
this.monitor.measureFunction(() => {
let sum = 0;
for (let i = 0; i < array.length; i++) {
sum += array[i];
}
return sum;
}, `for-loop-${size}`);
// Test forEach
this.monitor.measureFunction(() => {
let sum = 0;
array.forEach(item => sum += item);
return sum;
}, `forEach-${size}`);
// Test reduce
this.monitor.measureFunction(() => {
return array.reduce((sum, item) => sum + item, 0);
}, `reduce-${size}`);
// Test for...of
this.monitor.measureFunction(() => {
let sum = 0;
for (const item of array) {
sum += item;
}
return sum;
}, `for-of-${size}`);
});
return this.monitor.getMetrics();
}
// Test memoization
testMemoization() {
const expensiveFunction = (n) => {
// Simulate expensive computation
let result = 0;
for (let i = 0; i < n * 1000000; i++) {
result += Math.random();
}
return result;
};
const memoizedFunction = this.optimizer.memoize(expensiveFunction);
// First call (expensive)
this.monitor.measureFunction(expensiveFunction, 'expensive-first', 5);
// Second call (cached)
this.monitor.measureFunction(memoizedFunction, 'memoized-second', 5);
return this.monitor.getMetrics();
}
// Test debouncing and throttling
testEventHandling() {
const expensiveHandler = () => {
// Simulate expensive event handling
const start = performance.now();
while (performance.now() - start < 10) {
// Busy wait for 10ms
}
};
const debouncedHandler = this.optimizer.debounce(expensiveHandler, 100);
const throttledHandler = this.optimizer.throttle(expensiveHandler, 100);
// Simulate rapid events
for (let i = 0; i < 10; i++) {
debouncedHandler();
throttledHandler();
}
return 'Event handling optimization tested';
}
runAllTests() {
console.log('๐ Running Performance Test Suite...\n');
console.log('=== Array Operations Test ===');
const arrayResults = this.testArrayOperations();
arrayResults.forEach(result => {
if (result.duration) {
console.log(`${result.name}: ${result.duration.toFixed(2)}ms`);
}
});
console.log('\n=== Memoization Test ===');
const memoResults = this.testMemoization();
memoResults.forEach(result => {
if (result.duration) {
console.log(`${result.name}: ${result.duration.toFixed(2)}ms`);
}
});
console.log('\n=== Event Handling Test ===');
console.log(this.testEventHandling());
console.log('\n=== Overall Performance Metrics ===');
console.log(`Average duration: ${this.monitor.getAverageDuration().toFixed(2)}ms`);
const memoryUsage = this.optimizer.memoryManager.getMemoryUsage();
if (memoryUsage) {
console.log(`Memory usage: ${(memoryUsage.usedJSHeapSize / 1024 / 1024).toFixed(2)}MB`);
}
return this.monitor.getMetrics();
}
}
// Usage Example
function demonstratePerformanceOptimization() {
console.log('=== Performance Optimization Demo ===\n');
const testSuite = new PerformanceTestSuite();
const results = testSuite.runAllTests();
// Create a virtual scroller example
const virtualScroller = testSuite.optimizer.createVirtualScroller(50, 400, 10000);
console.log('\n=== Virtual Scrolling Example ===');
console.log('Visible range at scroll position 0:', virtualScroller.getVisibleRange(0));
console.log('Visible range at scroll position 1000:', virtualScroller.getVisibleRange(1000));
// Test lazy loading
const lazyData = testSuite.optimizer.createLazyLoader(() => {
console.log('Loading expensive data...');
return Array.from({ length: 1000 }, (_, i) => `Item ${i}`);
});
console.log('\n=== Lazy Loading Test ===');
console.log('Data loaded:', lazyData.isLoaded());
const data = lazyData.get();
console.log('Data loaded:', lazyData.isLoaded());
console.log('Data length:', data.length);
}
// Run demonstration
if (typeof window !== 'undefined') {
window.demonstratePerformanceOptimization = demonstratePerformanceOptimization;
} else {
demonstratePerformanceOptimization();
}