JavaScript Performance Optimization
Heavy scripts and unoptimized DOM updates block the main execution thread, causing sluggish UI interactions. Writing high-performance JavaScript is key to maintaining a smooth user experience.
1. Debounce and Throttle
Some events (like scrolling, resizing, mouse movements, or text typing) fire dozens of times per second. Executing complex calculations or network requests on every tick degrades performance.
A. Debounce: Delay Execution
Debouncing guarantees a function runs only once after a specified delay has passed since the last trigger. Perfect for search autocomplete inputs.
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId); // Cancel previous timer
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Example: Autocomplete API Request
const handleSearchInput = debounce((e) => {
console.log("Fetching results for:", e.target.value);
}, 300); // Wait for 300ms of silence before running
document.querySelector('input').addEventListener('input', handleSearchInput);B. Throttle: Limit Rate
Throttling guarantees a function is called at most once within a specified time window. Perfect for scroll position trackers.
function throttle(func, limit) {
let inThrottle = false;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
// Example: Scroll position tracker
const handleScroll = throttle(() => {
console.log("Current scroll height:", window.scrollY);
}, 200); // Executes at most once every 200ms
window.addEventListener('scroll', handleScroll);2. Minimizing DOM Queries & Batching Writes
The DOM is slow. Accessing and writing to elements causes browser layout recalculations (reflows).
A. Cache DOM Queries
Avoid querying the DOM inside loops:
// AVOID:
for (let i = 0; i < 100; i++) {
document.querySelector('#score').textContent = i; // Queries DOM 100 times
}
// BETTER:
const scoreEl = document.querySelector('#score'); // Query once
for (let i = 0; i < 100; i++) {
scoreEl.textContent = i;
}B. Batch DOM Insertions: DocumentFragment
If you need to insert multiple elements, don't append them one-by-one. Each append triggers a reflow. Instead, use a DocumentFragment which acts as a lightweight, invisible container:
const list = document.querySelector('#item-list');
const fragment = document.createDocumentFragment(); // Virtual DOM node
for (let i = 0; i < 10; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li); // Appended in memory, no reflow!
}
list.appendChild(fragment); // Single append triggers only ONE reflow!3. Preventing Memory Leaks
A memory leak occurs when JS holds objects in memory that are no longer needed, eventually crashing the page.
- Remove Event Listeners: If you delete a DOM element from the page, make sure to clean up any event listeners attached to it.
- Avoid Globals: Variables attached to
window(global scope) are never garbage collected. Always scope variables withletorconst. - Clean up Intervals: Always clear
setIntervaltimers when they are finished:
const intervalId = setInterval(() => { ... }, 1000);
// When done:
clearInterval(intervalId);In the next module, we will apply these skills to build real-world Vanilla JS projects.