Key Takeaways
constandletreplacevar— block scoping prevents subtle bugs- Template literals, destructuring, and optional chaining improve code readability by 40%
- Event delegation reduces memory usage by attaching one listener instead of hundreds
- Always validate and sanitize user input on both client AND server to prevent XSS attacks
- Use
deferandasyncstrategically to prevent render-blocking JavaScript
Table of Contents
JavaScript powers the interactive web. From simple form validation to complex single-page applications, the quality of your JavaScript code directly impacts performance, security, maintainability, and user experience. While frameworks like React and Vue abstract away many common patterns, understanding vanilla JavaScript best practices remains essential — especially when building lightweight, dependency-free websites like Renvima templates.
In this guide, we cover the modern JavaScript patterns and practices that every developer should adopt for production-quality code in 2026.
1. Modern ES6+ Syntax Essentials
Use const and let — Never var
The var keyword uses function scoping, which leads to subtle bugs with variable hoisting and unintended mutations. const and let use block scoping, which is predictable and safe:
// Bad: var hoists and leaks across blocks
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100); // Prints 5, 5, 5, 5, 5
}
// Good: let creates a new binding for each iteration
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100); // Prints 0, 1, 2, 3, 4
}
// Use const by default — only use let when reassignment is needed
const API_URL = '/api/templates';
const config = { theme: 'dark', lang: 'en' }; // Object is mutable, reference is not
let currentPage = 1; // Will be reassigned during pagination
Template Literals
String concatenation with + is error-prone and hard to read. Template literals with backticks provide embedded expressions and multi-line support:
// Bad: concatenation
const greeting = 'Hello, ' + user.name + '! You have ' + count + ' templates.';
// Good: template literals
const greeting = `Hello, ${user.name}! You have ${count} templates.`;
// Multi-line HTML injection
const cardHTML = `
<article class="template-card">
<h3>${escapeHtml(template.name)}</h3>
<p>${escapeHtml(template.description)}</p>
</article>
`;
Destructuring and Spread
// Object destructuring
const { name, email, role = 'user' } = userData;
// Array destructuring
const [first, second, ...rest] = items;
// Spread for shallow cloning (never mutate the original)
const updatedSettings = { ...settings, theme: 'light' };
const allTemplates = [...freeTemplates, ...paidTemplates];
Optional Chaining and Nullish Coalescing
// Optional chaining: safely access deep properties
const city = user?.address?.city; // undefined if any part is null/undefined
// Nullish coalescing: provide defaults for null/undefined only
const theme = savedTheme ?? 'dark'; // Uses 'dark' only if savedTheme is null/undefined
// Note: || would also catch '' and 0, which may be valid values
2. Efficient DOM Manipulation
DOM manipulation is one of the most expensive operations in JavaScript. Minimizing DOM access and batching changes dramatically improves performance:
// Bad: Reading and writing in a loop (causes layout thrashing)
items.forEach(item => {
const height = container.offsetHeight; // Read (forces layout)
item.style.top = height + 'px'; // Write (invalidates layout)
});
// Good: Batch reads, then batch writes
const height = container.offsetHeight; // Single read
items.forEach(item => {
item.style.top = height + 'px'; // Batch writes
});
// Better: Use DocumentFragment for bulk insertions
const fragment = document.createDocumentFragment();
templates.forEach(template => {
const card = document.createElement('article');
card.className = 'template-card glass-panel';
card.innerHTML = `<h3>${escapeHtml(template.name)}</h3>`;
fragment.appendChild(card);
});
container.appendChild(fragment); // Single DOM insertion
3. Event Handling Patterns
Event Delegation
Instead of attaching listeners to every child element, attach one listener to the parent and use event bubbling. This is especially important for dynamically generated content:
// Bad: One listener per button (100 buttons = 100 listeners)
document.querySelectorAll('.download-btn').forEach(btn => {
btn.addEventListener('click', handleDownload);
});
// Good: One listener on the container
document.getElementById('templates-grid').addEventListener('click', (e) => {
const btn = e.target.closest('.download-btn');
if (!btn) return;
const templateId = btn.dataset.templateId;
handleDownload(templateId);
});
Debouncing and Throttling
// Debounce: Wait until the user stops typing (search input)
function debounce(fn, delay = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
const searchInput = document.getElementById('templateSearch');
searchInput.addEventListener('input', debounce((e) => {
filterTemplates(e.target.value);
}, 250));
// Throttle: Execute at most once per interval (scroll events)
function throttle(fn, limit = 100) {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= limit) {
lastCall = now;
fn(...args);
}
};
}
window.addEventListener('scroll', throttle(updateScrollProgress, 50));
4. Robust Error Handling
// Always wrap async operations in try/catch
async function fetchTemplates() {
try {
const response = await fetch('/api/templates');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Failed to fetch templates:', error.message);
showToast('Unable to load templates. Please try again.', 'error');
return []; // Return safe fallback
}
}
// Global error handler for uncaught errors
window.addEventListener('error', (event) => {
console.error('Uncaught error:', event.error);
// Send to error tracking service (Sentry, etc.)
});
// Global handler for unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
event.preventDefault(); // Prevent default browser behavior
});
5. Performance Patterns
Lazy Initialization
// Don't initialize heavy components until needed
const initMap = () => {
// Only load Google Maps when the user scrolls to the contact section
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadGoogleMaps();
observer.disconnect();
}
});
});
observer.observe(document.getElementById('contact-section'));
};
requestAnimationFrame for Visual Updates
// Bad: Direct style manipulation in scroll handler
window.addEventListener('scroll', () => {
header.style.transform = `translateY(${window.scrollY}px)`;
});
// Good: Use rAF to batch visual updates with the browser's paint cycle
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
header.style.transform = `translateY(${window.scrollY}px)`;
ticking = false;
});
ticking = true;
}
});
6. Security Best Practices
Prevent Cross-Site Scripting (XSS)
// NEVER insert user input directly into the DOM
// Bad: XSS vulnerability
element.innerHTML = userInput;
// Good: Escape HTML entities
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
element.innerHTML = escapeHtml(userInput);
// Better: Use textContent for plain text (no HTML parsing)
element.textContent = userInput;
Validate Input on Both Sides
Client-side validation improves UX; server-side validation is your security boundary. Never trust client-side validation alone — it can be bypassed trivially.
7. Code Organization
// Organize code with the module pattern
const TemplateManager = (() => {
// Private state
let templates = [];
let currentFilter = 'all';
// Private methods
const sortByName = (a, b) => a.name.localeCompare(b.name);
// Public API
return {
init() {
this.loadTemplates();
this.bindEvents();
},
async loadTemplates() {
templates = await fetchTemplates();
this.render();
},
setFilter(filter) {
currentFilter = filter;
this.render();
},
render() {
const filtered = currentFilter === 'all'
? templates
: templates.filter(t => t.category === currentFilter);
renderGrid(filtered.sort(sortByName));
},
bindEvents() {
document.getElementById('categoryFilter')
.addEventListener('change', (e) => this.setFilter(e.target.value));
}
};
})();
document.addEventListener('DOMContentLoaded', () => TemplateManager.init());
Conclusion
Writing production-quality JavaScript is about discipline, not complexity. Modern ES6+ syntax makes code more readable, event delegation improves performance, proper error handling prevents silent failures, and security-conscious coding protects your users.
At Renvima, every template ships with clean, well-organized vanilla JavaScript that follows these principles. No build tools required, no framework dependencies — just modern, efficient code that works everywhere.
See clean JavaScript in action.
Every Renvima template uses modern, dependency-free vanilla JS.
Browse Templates