Chapter 7: Memory Management and Performance
Chapter 7 of 15
Chapter 7: Memory Management and Performance
7.1 Memory Leaks
Memory leaks occur when memory that is no longer needed is not released. In JavaScript, this often happens due to closures, event listeners, or circular references.
Common Memory Leak Causes:
// 1. Event Listeners Not Removed
function addListener() {
const button = document.getElementById('button');
button.addEventListener('click', function() {
console.log('Clicked');
});
// Memory leak if button is removed but listener isn't
}
// Fix: Remove listeners
function addListenerFixed() {
const button = document.getElementById('button');
const handler = function() {
console.log('Clicked');
};
button.addEventListener('click', handler);
// Later, when removing button:
button.removeEventListener('click', handler);
}
Closure Memory Leaks:
// Leak: Large object kept in closure
function createHandler() {
const largeData = new Array(1000000).fill('data');
return function() {
// largeData is kept in memory even if not used
console.log('Handler called');
};
}
// Fix: Only keep what you need
function createHandlerFixed() {
return function() {
// Access largeData only when needed
const largeData = getLargeData();
console.log('Handler called');
};
}
Circular References:
// Circular reference
let obj1 = { name: 'obj1' };
let obj2 = { name: 'obj2' };
obj1.ref = obj2;
obj2.ref = obj1;
// Even if we set to null, memory might not be freed immediately
obj1 = null;
obj2 = null;
7.2 Performance Optimization
Debouncing:
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Usage: Limit function calls
const debouncedSearch = debounce((query) => {
console.log('Searching for:', query);
}, 300);
// User types quickly, but function only called once after 300ms
input.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
Throttling:
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Usage: Limit function execution rate
const throttledScroll = throttle(() => {
console.log('Scroll event');
}, 100);
window.addEventListener('scroll', throttledScroll);
Lazy Loading:
// Lazy load images
const lazyImages = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
});
});
lazyImages.forEach(img => imageObserver.observe(img));
Code Splitting:
// Dynamic imports for code splitting
async function loadModule() {
const module = await import('./heavy-module.js');
module.doSomething();
}
// React lazy loading
const LazyComponent = React.lazy(() => import('./LazyComponent'));
7.3 Performance Monitoring
// Performance API
const startTime = performance.now();
// ... do work ...
const endTime = performance.now();
console.log(`Execution time: ${endTime - startTime}ms`);
// Memory usage
if (performance.memory) {
console.log('Used:', performance.memory.usedJSHeapSize);
console.log('Total:', performance.memory.totalJSHeapSize);
console.log('Limit:', performance.memory.jsHeapSizeLimit);
}