Performance
6 min readRapid overview
- JavaScript Performance Optimization
- Table of Contents
- Memory Management
- Avoiding Memory Leaks
- Garbage Collection
- WeakMap and WeakSet
- DOM Manipulation
- Minimize Reflows and Repaints
- Document Fragments
- Read/Write Batching
- Event Handling
- Event Delegation
- Debouncing
- Throttling
- Passive Event Listeners
- Loops and Iterations
- Array Methods Performance
- Caching Length
- Early Termination
- Async Operations
- Parallel vs Sequential
- Request Batching
- Caching Promises
- Code Splitting and Lazy Loading
- Dynamic Imports
- Intersection Observer for Lazy Loading
- Measuring Performance
- Performance API
- User Timing API
- Console Timing
- Memory Profiling
- Best Practices Summary
- Tools for Performance Analysis
JavaScript Performance Optimization
Performance best practices and optimization techniques for JavaScript applications.
Table of Contents
- Memory Management
- DOM Manipulation
- Event Handling
- Loops and Iterations
- Async Operations
- Code Splitting and Lazy Loading
- Measuring Performance
Memory Management
Avoiding Memory Leaks
Common Memory Leak Causes:
- Global Variables:
// ❌ Bad - creates global variable
function leak() {
undeclaredVar = 'leaks to global scope';
}
// ✅ Good - use strict mode and proper declarations
'use strict';
function noLeak() {
const declaredVar = 'properly scoped';
}
- Event Listeners:
// ❌ Bad - doesn't remove listener
const button = document.getElementById('btn');
button.addEventListener('click', handleClick);
// ✅ Good - remove when done
function cleanup() {
button.removeEventListener('click', handleClick);
}
- Timers:
// ❌ Bad - timer keeps running
const intervalId = setInterval(() => {
console.log('Running...');
}, 1000);
// ✅ Good - clear when done
clearInterval(intervalId);
- Closures Holding References:
// ❌ Bad - closure holds large object
function createHandler() {
const largeObject = new Array(1000000);
return function() {
console.log(largeObject.length);
};
}
// ✅ Good - only store what you need
function createHandler() {
const length = new Array(1000000).length;
return function() {
console.log(length);
};
}
Garbage Collection
// Help GC by nullifying references
let largeObject = { /* ... */ };
// ... use largeObject ...
largeObject = null; // Allow GC to reclaim memory
WeakMap and WeakSet
// WeakMap allows garbage collection of keys
const cache = new WeakMap();
let obj = { data: 'important' };
cache.set(obj, 'metadata');
// When obj is no longer referenced elsewhere,
// both the key and value can be garbage collected
obj = null;
// Regular Map would prevent GC
const regularCache = new Map();
// Keys won't be garbage collected even if not used
DOM Manipulation
Minimize Reflows and Repaints
// ❌ Bad - causes multiple reflows
const element = document.getElementById('item');
element.style.width = '100px'; // reflow
element.style.height = '100px'; // reflow
element.style.margin = '10px'; // reflow
// ✅ Good - batch changes
element.style.cssText = 'width: 100px; height: 100px; margin: 10px;';
// Or use classes
element.className = 'optimized-style';
Document Fragments
// ❌ Bad - multiple DOM manipulations
const list = document.getElementById('list');
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
list.appendChild(li); // causes reflow each time
}
// ✅ Good - use DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
list.appendChild(fragment); // single reflow
Read/Write Batching
// ❌ Bad - interleaved reads and writes (thrashing)
const elements = document.querySelectorAll('.item');
elements.forEach(el => {
const height = el.offsetHeight; // read (forces layout)
el.style.height = (height + 10) + 'px'; // write
});
// ✅ Good - batch reads, then writes
const heights = Array.from(elements).map(el => el.offsetHeight);
elements.forEach((el, i) => {
el.style.height = (heights[i] + 10) + 'px';
});
Event Handling
Event Delegation
// ❌ Bad - multiple event listeners
document.querySelectorAll('.item').forEach(item => {
item.addEventListener('click', handleClick);
});
// ✅ Good - single delegated listener
document.getElementById('list').addEventListener('click', (e) => {
if (e.target.classList.contains('item')) {
handleClick(e);
}
});
Debouncing
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Usage
const handleResize = debounce(() => {
console.log('Window resized');
}, 250);
window.addEventListener('resize', handleResize);
Throttling
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Usage
const handleScroll = throttle(() => {
console.log('Scrolling...');
}, 100);
window.addEventListener('scroll', handleScroll);
Passive Event Listeners
// ✅ Good - improves scroll performance
document.addEventListener('touchstart', handler, { passive: true });
document.addEventListener('wheel', handler, { passive: true });
Loops and Iterations
Array Methods Performance
const arr = new Array(1000000).fill(0);
// Fastest
for (let i = 0; i < arr.length; i++) { /* ... */ }
// Slightly slower but more readable
for (const item of arr) { /* ... */ }
// Slowest but most expressive
arr.forEach(item => { /* ... */ });
// For filtering/mapping, native methods are optimized
const doubled = arr.map(x => x * 2);
const evens = arr.filter(x => x % 2 === 0);
Caching Length
// ❌ Bad - recalculates length each iteration
for (let i = 0; i < array.length; i++) {
// ...
}
// ✅ Good - cache length
const len = array.length;
for (let i = 0; i < len; i++) {
// ...
}
Early Termination
// ✅ Use early returns/breaks
function findItem(arr, predicate) {
for (let i = 0; i < arr.length; i++) {
if (predicate(arr[i])) {
return arr[i]; // stop as soon as found
}
}
return null;
}
// ✅ Use .some() for existence checks
const hasEven = arr.some(x => x % 2 === 0);
// ✅ Use .find() instead of .filter()[0]
const firstEven = arr.find(x => x % 2 === 0);
Async Operations
Parallel vs Sequential
// ❌ Sequential - slow
async function sequential() {
const result1 = await fetch('/api/1');
const result2 = await fetch('/api/2');
const result3 = await fetch('/api/3');
return [result1, result2, result3];
}
// ✅ Parallel - fast
async function parallel() {
const [result1, result2, result3] = await Promise.all([
fetch('/api/1'),
fetch('/api/2'),
fetch('/api/3')
]);
return [result1, result2, result3];
}
Request Batching
class RequestBatcher {
constructor(batchFn, delay = 10) {
this.batchFn = batchFn;
this.delay = delay;
this.queue = [];
this.timeoutId = null;
}
request(id) {
return new Promise((resolve, reject) => {
this.queue.push({ id, resolve, reject });
if (!this.timeoutId) {
this.timeoutId = setTimeout(() => {
this.flush();
}, this.delay);
}
});
}
flush() {
const batch = this.queue.splice(0);
this.timeoutId = null;
const ids = batch.map(item => item.id);
this.batchFn(ids)
.then(results => {
batch.forEach((item, i) => {
item.resolve(results[i]);
});
})
.catch(error => {
batch.forEach(item => item.reject(error));
});
}
}
// Usage
const batcher = new RequestBatcher(async (ids) => {
const response = await fetch(`/api/items?ids=${ids.join(',')}`);
return response.json();
});
batcher.request(1);
batcher.request(2);
batcher.request(3);
// All three requests batched into single API call
Caching Promises
const promiseCache = new Map();
function cachedFetch(url) {
if (promiseCache.has(url)) {
return promiseCache.get(url);
}
const promise = fetch(url).then(res => res.json());
promiseCache.set(url, promise);
return promise;
}
Code Splitting and Lazy Loading
Dynamic Imports
// Load module only when needed
async function loadFeature() {
const module = await import('./heavy-feature.js');
module.initialize();
}
// Conditional loading
if (userWantsDarkMode) {
import('./dark-theme.js').then(theme => theme.apply());
}
Intersection Observer for Lazy Loading
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
Measuring Performance
Performance API
// Measure function execution time
performance.mark('start');
expensiveOperation();
performance.mark('end');
performance.measure('operation', 'start', 'end');
const measure = performance.getEntriesByName('operation')[0];
console.log(`Operation took ${measure.duration}ms`);
// Clean up
performance.clearMarks();
performance.clearMeasures();
User Timing API
function measureAsync() {
performance.mark('fetch-start');
fetch('/api/data')
.then(response => response.json())
.then(data => {
performance.mark('fetch-end');
performance.measure('fetch-duration', 'fetch-start', 'fetch-end');
const measure = performance.getEntriesByName('fetch-duration')[0];
console.log(`Fetch took ${measure.duration}ms`);
});
}
Console Timing
console.time('operation');
expensiveOperation();
console.timeEnd('operation');
// Logs: operation: 123.456ms
Memory Profiling
// Check memory usage (Chrome only)
if (performance.memory) {
console.log({
usedJSHeapSize: performance.memory.usedJSHeapSize / 1048576 + ' MB',
totalJSHeapSize: performance.memory.totalJSHeapSize / 1048576 + ' MB',
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit / 1048576 + ' MB'
});
}
Best Practices Summary
- Minimize DOM Access - Cache DOM references
- Batch DOM Changes - Use fragments, cssText, classes
- Debounce/Throttle Events - For scroll, resize, input
- Use Event Delegation - One listener instead of many
- Lazy Load Resources - Load only what's needed
- Cache Results - Memoize expensive computations
- Optimize Loops - Cache length, use appropriate methods
- Parallel Async Operations - Use Promise.all()
- Avoid Memory Leaks - Remove listeners, clear timers
- Measure Performance - Use Performance API to identify bottlenecks
Tools for Performance Analysis
- Chrome DevTools Performance Panel
- Lighthouse
- WebPageTest
- Performance API
- Chrome DevTools Memory Profiler
- React DevTools Profiler (for React apps)
- Angular DevTools (for Angular apps)