JavaScript Best Practices
Essential guidelines for writing clean, maintainable, and efficient JavaScript code.
Code Organization and Structure
javascript// Use meaningful variable and function names // Bad const d = new Date(); const u = users.filter(x => x.a > 18); // Good const currentDate = new Date(); const adultUsers = users.filter(user => user.age > 18); // Use const by default, let when reassignment is needed const API_URL = 'https://api.example.com'; const userPreferences = { theme: 'dark' }; let currentUser = null; // Will be reassigned // Group related functionality const UserService = { async getUser(id) { const response = await fetch(`${API_URL}/users/${id}`); return response.json(); }, async updateUser(id, data) { const response = await fetch(`${API_URL}/users/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); return response.json(); } }; // Use early returns to reduce nesting // Bad function processUser(user) { if (user) { if (user.isActive) { if (user.hasPermission) { return performAction(user); } else { throw new Error('No permission'); } } else { throw new Error('User inactive'); } } else { throw new Error('User not found'); } } // Good function processUser(user) { if (!user) { throw new Error('User not found'); } if (!user.isActive) { throw new Error('User inactive'); } if (!user.hasPermission) { throw new Error('No permission'); } return performAction(user); }
Error Handling Best Practices
javascript// Always handle errors appropriately async function fetchUserData(userId) { try { const response = await fetch(`/api/users/${userId}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error('Failed to fetch user data:', error); // Return safe fallback or re-throw with context if (error.name === 'TypeError') { throw new Error('Network error: Please check your connection'); } throw error; } } // Create custom error types class ValidationError extends Error { constructor(message, field) { super(message); this.name = 'ValidationError'; this.field = field; } } function validateEmail(email) { if (!email) { throw new ValidationError('Email is required', 'email'); } if (!email.includes('@')) { throw new ValidationError('Invalid email format', 'email'); } } // Use error boundaries in applications function withErrorHandling(fn) { return async function(...args) { try { return await fn.apply(this, args); } catch (error) { console.error(`Error in ${fn.name}:, error`); // Log to monitoring service // Show user-friendly message throw error; } }; }
Performance Best Practices
javascript// Avoid global variables // Bad var globalCounter = 0; function increment() { globalCounter++; } // Good const Counter = (function() { let count = 0; return { increment() { count++; }, getCount() { return count; } }; })(); // Use efficient array methods const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // Find first match efficiently const firstEven = numbers.find(n => n % 2 === 0); // Stops at first match // Use appropriate method for the task const hasEvenNumber = numbers.some(n => n % 2 === 0); // Better than filter().length > 0 const allEven = numbers.every(n => n % 2 === 0); // Cache expensive operations const memoize = (fn) => { const cache = new Map(); return function(...args) { const key = JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result = fn.apply(this, args); cache.set(key, result); return result; }; }; const expensiveCalculation = memoize((n) => { console.log(`Calculating for ${n}`); return n * n * n; }); // Debounce expensive operations function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => func.apply(this, args), delay); }; } const debouncedSearch = debounce((query) => { // Expensive search operation console.log(`Searching for: ${query}`); }, 300);
Security Best Practices
javascript// Validate and sanitize input function sanitizeInput(input) { if (typeof input !== 'string') { return ''; } return input .trim() .replace(/[<>]/g, '') // Remove potential HTML tags .substring(0, 1000); // Limit length } // Avoid eval() and similar functions // Bad const userCode = "alert('XSS')"; eval(userCode); // Never do this! // Good - use safer alternatives const allowedOperations = { add: (a, b) => a + b, subtract: (a, b) => a - b, multiply: (a, b) => a * b }; function safeCalculate(operation, a, b) { const fn = allowedOperations[operation]; if (!fn) { throw new Error('Invalid operation'); } return fn(a, b); } // Secure API calls async function secureApiCall(endpoint, data) { const token = localStorage.getItem('authToken'); if (!token) { throw new Error('Authentication required'); } const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, 'X-Requested-With': 'XMLHttpRequest' // CSRF protection }, body: JSON.stringify(data) }); if (response.status === 401) { // Handle token expiration localStorage.removeItem('authToken'); window.location.href = '/login'; return; } return response.json(); } // Content Security Policy helpers function createSecureElement(tag, attributes = {}) { const element = document.createElement(tag); // Whitelist allowed attributes const allowedAttributes = ['id', 'class', 'data-*', 'aria-*']; Object.entries(attributes).forEach(([key, value]) => { if (allowedAttributes.some(attr => attr.endsWith('*') ? key.startsWith(attr.slice(0, -1)) : key === attr )) { element.setAttribute(key, value); } }); return element; }
Code Quality and Maintainability
javascript// Write self-documenting code // Bad function calc(x, y, z) { return x * y * z * 0.1; } // Good function calculateTotalPriceWithTax(price, quantity, taxRate) { const subtotal = price * quantity; const tax = subtotal * taxRate; return subtotal + tax; } // Use JSDoc for complex functions /** * Processes user data and returns formatted result * @param {Object} userData - The user data object * @param {string} userData.name - User's full name * @param {number} userData.age - User's age * @param {string[]} userData.roles - User's roles * @param {Object} options - Processing options * @param {boolean} options.includeMetadata - Whether to include metadata * @returns {Promise<Object>} Formatted user data * @throws {ValidationError} When user data is invalid */ async function processUserData(userData, options = {}) { validateUserData(userData); const processed = { displayName: userData.name.toUpperCase(), isAdult: userData.age >= 18, permissions: calculatePermissions(userData.roles) }; if (options.includeMetadata) { processed.metadata = await fetchUserMetadata(userData.id); } return processed; } // Use configuration objects for functions with many parameters // Bad function createUser(name, email, age, role, department, isActive, permissions) { // Too many parameters } // Good function createUser(config) { const { name, email, age, role = 'user', department = 'general', isActive = true, permissions = [] } = config; return { name, email, age, role, department, isActive, permissions, createdAt: new Date() }; } // Usage const user = createUser({ name: 'John Doe', email: 'john@example.com', age: 30, role: 'admin' });
Modern JavaScript Patterns
javascript// Use async/await instead of Promise chains // Bad function fetchUserPosts(userId) { return fetch(`/api/users/${userId}`) .then(response => response.json()) .then(user => fetch(`/api/users/${user.id}/posts`)) .then(response => response.json()) .catch(error => { console.error('Error:', error); throw error; }); } // Good async function fetchUserPosts(userId) { try { const userResponse = await fetch(`/api/users/${userId}`); const user = await userResponse.json(); const postsResponse = await fetch(`/api/users/${user.id}/posts`); return await postsResponse.json(); } catch (error) { console.error('Error fetching user posts:', error); throw error; } } // Use destructuring for cleaner code // Bad function displayUser(user) { const name = user.name; const email = user.email; const age = user.age; return `${name} (${email}) - Age: ${age}`; } // Good function displayUser({ name, email, age }) { return `${name} (${email}) - Age: ${age}`; } // Use template literals for string formatting // Bad const message = 'Hello ' + user.name + ', you have ' + notifications.length + ' new notifications.'; // Good const message = `Hello ${user.name}, you have ${notifications.length} new notifications.`; // Use optional chaining and nullish coalescing // Bad const userCity = user && user.address && user.address.city ? user.address.city : 'Unknown'; // Good const userCity = user?.address?.city ?? 'Unknown'; // Use modules for better organization // userService.js export class UserService { static async getUser(id) { const response = await fetch(`/api/users/${id}`); return response.json(); } static async updateUser(id, data) { const response = await fetch(`/api/users/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); return response.json(); } } // main.js import { UserService } from './userService.js'; const user = await UserService.getUser(123);
Testing and Debugging
javascript// Write testable functions // Bad - hard to test function processFormData() { const form = document.getElementById('userForm'); const data = new FormData(form); fetch('/api/users', { method: 'POST', body: data }).then(response => { if (response.ok) { alert('Success!'); } else { alert('Error!'); } }); } // Good - testable function extractFormData(formElement) { const data = new FormData(formElement); const result = {}; for (const [key, value] of data.entries()) { result[key] = value; } return result; } async function submitUserData(userData) { const response = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(userData) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); } function handleFormSubmit(formElement, onSuccess, onError) { const userData = extractFormData(formElement); submitUserData(userData) .then(onSuccess) .catch(onError); } // Add debugging helpers const debug = { log: (message, data) => { if (process.env.NODE_ENV === 'development') { console.log(`[DEBUG] ${message}`, data); } }, time: (label) => { if (process.env.NODE_ENV === 'development') { console.time(label); } }, timeEnd: (label) => { if (process.env.NODE_ENV === 'development') { console.timeEnd(label); } } }; // Usage debug.time('API Call'); const result = await fetchUserData(123); debug.timeEnd('API Call'); debug.log('User data fetched', result);
Code Style and Formatting
javascript// Use consistent naming conventions const API_BASE_URL = 'https://api.example.com'; // SCREAMING_SNAKE_CASE for constants const userPreferences = {}; // camelCase for variables const UserService = {}; // PascalCase for classes/constructors // Use meaningful boolean names const isUserLoggedIn = checkAuthStatus(); const hasPermission = user.roles.includes('admin'); const canEdit = isUserLoggedIn && hasPermission; // Group imports logically // Third-party imports import React from 'react'; import axios from 'axios'; // Internal imports import { UserService } from './services/UserService'; import { validateEmail } from './utils/validation'; // Use consistent indentation and spacing const userConfig = { name: 'John Doe', preferences: { theme: 'dark', notifications: { email: true, push: false, sms: true } }, roles: ['user', 'editor'] }; // Use trailing commas for better diffs const colors = [ 'red', 'green', 'blue', // Trailing comma makes adding items easier ]; // Use semicolons consistently const message = 'Hello World'; // Always use semicolons const result = calculateTotal(items); // Prevents ASI issues