JavaScript Essentials

JavaScript Best Practices & Performance Optimization

50 min Lesson 60 of 60

JavaScript Best Practices & Performance Optimization

Welcome to the final lesson of our JavaScript Essentials course! In this comprehensive lesson, we'll explore best practices, performance optimization techniques, security considerations, and strategies for writing maintainable, efficient JavaScript code. These practices will help you become a professional JavaScript developer.

1. Code Organization and Structure

File Organization

Organize your code into logical modules and files:

<!-- Bad: Everything in one file -->
<script>
    // 5000 lines of mixed code...
</script>

<!-- Good: Organized structure -->
project/
├── src/
│   ├── components/
│   │   ├── header.js
│   │   └── footer.js
│   ├── utils/
│   │   ├── validation.js
│   │   └── formatting.js
│   ├── services/
│   │   └── api.js
│   └── app.js
└── index.html

Module Pattern

Use ES6 modules to organize code:

// utils/validation.js
export function validateEmail(email) {
    const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return re.test(email);
}

export function validatePhone(phone) {
    const re = /^\+?[1-9]\d{1,14}$/;
    return re.test(phone);
}

// app.js
import { validateEmail, validatePhone } from './utils/validation.js';

const email = 'user@example.com';
console.log(validateEmail(email)); // true

Naming Conventions

Follow consistent naming conventions:

// Variables and functions: camelCase
let userName = 'John';
function getUserData() { }

// Constants: UPPER_SNAKE_CASE
const MAX_RETRY_ATTEMPTS = 3;
const API_BASE_URL = 'https://api.example.com';

// Classes: PascalCase
class UserProfile { }
class ShoppingCart { }

// Private properties: prefix with underscore
class BankAccount {
    constructor() {
        this._balance = 0; // Private convention
    }
}

// Boolean variables: use is/has/can prefix
let isActive = true;
let hasPermission = false;
let canEdit = true;
Tip: Consistent naming conventions make your code more readable and help other developers (including future you) understand the purpose of variables and functions at a glance.
Note: This lesson summarizes the best practices covered throughout the entire JavaScript Essentials course. Refer back to individual lessons for deeper coverage of each topic.

2. Variable Best Practices

Prefer const, Use let, Avoid var

Always use const by default, only use let when you need reassignment:

// Bad: Using var
var count = 0;
var name = 'John';

// Good: Use const by default
const name = 'John';
const MAX_ITEMS = 100;
const config = { theme: 'dark' };

// Good: Use let when reassignment needed
let count = 0;
count++;

let status = 'pending';
status = 'completed';

Meaningful Variable Names

Use descriptive names that reveal intent:

// Bad: Unclear names
let d = new Date();
let x = users.filter(u => u.a);
let temp = calculate();

// Good: Descriptive names
let currentDate = new Date();
let activeUsers = users.filter(user => user.isActive);
let totalPrice = calculateTotalPrice();

// Bad: Single letter (except loops)
let a = 5;
let b = 10;

// Good: Descriptive
let width = 5;
let height = 10;

// Acceptable: Common loop variables
for (let i = 0; i < items.length; i++) {
    console.log(items[i]);
}

Avoid Global Variables

Minimize global scope pollution:

// Bad: Global variables
var userData = {};
var isLoggedIn = false;

function login() {
    isLoggedIn = true;
}

// Good: Use modules or IIFE
const App = (function() {
    let userData = {};
    let isLoggedIn = false;

    function login() {
        isLoggedIn = true;
    }

    return {
        login
    };
})();

// Better: ES6 modules
// auth.js
let isLoggedIn = false;

export function login() {
    isLoggedIn = true;
}

export function isAuthenticated() {
    return isLoggedIn;
}

3. Function Best Practices

Single Responsibility Principle

Each function should do one thing well:

// Bad: Function doing too much
function processUserData(user) {
    // Validate
    if (!user.email) return false;

    // Transform
    user.name = user.name.toUpperCase();

    // Save to database
    database.save(user);

    // Send email
    emailService.send(user.email);

    // Log
    console.log('User processed');
}

// Good: Separate concerns
function validateUser(user) {
    return user.email !== undefined;
}

function normalizeUserData(user) {
    return {
        ...user,
        name: user.name.toUpperCase()
    };
}

function saveUser(user) {
    return database.save(user);
}

function sendWelcomeEmail(user) {
    return emailService.send(user.email);
}

function processUser(user) {
    if (!validateUser(user)) {
        throw new Error('Invalid user data');
    }

    const normalized = normalizeUserData(user);
    const saved = saveUser(normalized);
    sendWelcomeEmail(saved);

    console.log('User processed successfully');
    return saved;
}

Pure Functions

Write pure functions when possible (no side effects, same input = same output):

// Impure: Modifies external state
let total = 0;
function addToTotal(value) {
    total += value; // Side effect
    return total;
}

// Pure: No side effects
function add(a, b) {
    return a + b;
}

// Impure: Modifies input
function addItem(cart, item) {
    cart.items.push(item); // Mutates input
    return cart;
}

// Pure: Returns new object
function addItem(cart, item) {
    return {
        ...cart,
        items: [...cart.items, item]
    };
}

Function Size and Complexity

Keep functions small and focused:

// Bad: Long, complex function
function calculateOrderTotal(order) {
    let subtotal = 0;
    for (let item of order.items) {
        subtotal += item.price * item.quantity;
    }

    let discount = 0;
    if (order.couponCode) {
        if (order.couponCode === 'SAVE10') {
            discount = subtotal * 0.1;
        } else if (order.couponCode === 'SAVE20') {
            discount = subtotal * 0.2;
        }
    }

    let tax = 0;
    if (order.country === 'US') {
        tax = (subtotal - discount) * 0.08;
    } else if (order.country === 'UK') {
        tax = (subtotal - discount) * 0.20;
    }

    return subtotal - discount + tax;
}

// Good: Broken into smaller functions
function calculateSubtotal(items) {
    return items.reduce((sum, item) =>
        sum + (item.price * item.quantity), 0);
}

function calculateDiscount(subtotal, couponCode) {
    const discounts = {
        'SAVE10': 0.1,
        'SAVE20': 0.2
    };

    return subtotal * (discounts[couponCode] || 0);
}

function calculateTax(amount, country) {
    const taxRates = {
        'US': 0.08,
        'UK': 0.20
    };

    return amount * (taxRates[country] || 0);
}

function calculateOrderTotal(order) {
    const subtotal = calculateSubtotal(order.items);
    const discount = calculateDiscount(subtotal, order.couponCode);
    const taxableAmount = subtotal - discount;
    const tax = calculateTax(taxableAmount, order.country);

    return taxableAmount + tax;
}
Tip: If a function is longer than your screen, it's probably too long. Break it into smaller, focused functions.

4. Error Handling Strategy

Consistent Error Handling

Use try-catch blocks appropriately:

// Bad: Silent failures
function loadUserData(userId) {
    try {
        const data = localStorage.getItem(userId);
        return JSON.parse(data);
    } catch (e) {
        // Silent failure - bad!
    }
}

// Good: Proper error handling
function loadUserData(userId) {
    try {
        const data = localStorage.getItem(userId);
        if (!data) {
            throw new Error('User data not found');
        }
        return JSON.parse(data);
    } catch (error) {
        console.error('Failed to load user data:', error);
        throw error; // Re-throw or handle appropriately
    }
}

// Async error handling
async function fetchUserProfile(userId) {
    try {
        const response = await fetch(`/api/users/${userId}`);

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        const data = await response.json();
        return data;
    } catch (error) {
        console.error('Error fetching user profile:', error);
        throw error;
    }
}

Custom Error Classes

Create custom errors for better error handling:

// Define custom error classes
class ValidationError extends Error {
    constructor(message, field) {
        super(message);
        this.name = 'ValidationError';
        this.field = field;
    }
}

class NetworkError extends Error {
    constructor(message, statusCode) {
        super(message);
        this.name = 'NetworkError';
        this.statusCode = statusCode;
    }
}

// Use custom errors
function validateEmail(email) {
    if (!email) {
        throw new ValidationError('Email is required', 'email');
    }

    if (!email.includes('@')) {
        throw new ValidationError('Invalid email format', 'email');
    }

    return true;
}

// Handle specific error types
try {
    validateEmail('');
} catch (error) {
    if (error instanceof ValidationError) {
        console.log(`Validation failed for ${error.field}: ${error.message}`);
    } else {
        console.error('Unexpected error:', error);
    }
}

5. Performance Optimization

DOM Manipulation Best Practices

Minimize DOM access and batch updates:

// Bad: Multiple DOM manipulations
function addItems(items) {
    const list = document.getElementById('list');

    items.forEach(item => {
        const li = document.createElement('li');
        li.textContent = item;
        list.appendChild(li); // Triggers reflow each time
    });
}

// Good: Batch DOM updates
function addItems(items) {
    const list = document.getElementById('list');
    const fragment = document.createDocumentFragment();

    items.forEach(item => {
        const li = document.createElement('li');
        li.textContent = item;
        fragment.appendChild(li);
    });

    list.appendChild(fragment); // Single reflow
}

// Better: Use innerHTML for large lists
function addItems(items) {
    const list = document.getElementById('list');
    const html = items.map(item => `<li>${item}</li>`).join('');
    list.innerHTML = html;
}

Event Delegation

Use event delegation for better performance:

// Bad: Adding listeners to each item
function attachListeners() {
    const buttons = document.querySelectorAll('.item-button');

    buttons.forEach(button => {
        button.addEventListener('click', handleClick);
    });
}

// Good: Event delegation
function attachListeners() {
    const container = document.getElementById('items-container');

    container.addEventListener('click', (e) => {
        if (e.target.classList.contains('item-button')) {
            handleClick(e);
        }
    });
}

// Works for dynamically added elements too
function addNewItem() {
    const container = document.getElementById('items-container');
    const button = document.createElement('button');
    button.className = 'item-button';
    button.textContent = 'New Item';
    container.appendChild(button);
    // No need to attach listener - delegation handles it!
}

Debouncing and Throttling

Control function execution frequency:

// Debounce: Execute after delay since last call
function debounce(func, delay) {
    let timeoutId;

    return function(...args) {
        clearTimeout(timeoutId);

        timeoutId = setTimeout(() => {
            func.apply(this, args);
        }, delay);
    };
}

// Usage: Search as user types
const searchInput = document.getElementById('search');
const performSearch = debounce((query) => {
    console.log('Searching for:', query);
    // API call here
}, 300);

searchInput.addEventListener('input', (e) => {
    performSearch(e.target.value);
});

// Throttle: Execute at most once per interval
function throttle(func, limit) {
    let inThrottle;

    return function(...args) {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;

            setTimeout(() => {
                inThrottle = false;
            }, limit);
        }
    };
}

// Usage: Scroll event
const handleScroll = throttle(() => {
    console.log('Scroll position:', window.scrollY);
}, 100);

window.addEventListener('scroll', handleScroll);

Avoid Memory Leaks

Clean up event listeners and references:

// Bad: Memory leak
function attachListener() {
    const button = document.getElementById('myButton');

    button.addEventListener('click', function() {
        // This creates a closure that may prevent garbage collection
        const largeData = new Array(1000000);
        console.log(largeData);
    });
}

// Good: Clean up
class Component {
    constructor(element) {
        this.element = element;
        this.handleClick = this.handleClick.bind(this);
        this.element.addEventListener('click', this.handleClick);
    }

    handleClick() {
        console.log('Clicked');
    }

    destroy() {
        // Remove event listener
        this.element.removeEventListener('click', this.handleClick);

        // Clear references
        this.element = null;
        this.handleClick = null;
    }
}

// Usage
const component = new Component(document.getElementById('myButton'));

// Later, when component is no longer needed
component.destroy();

RequestAnimationFrame for Animations

Use requestAnimationFrame for smooth animations:

// Bad: Using setTimeout
function animate() {
    const element = document.getElementById('box');
    let position = 0;

    setInterval(() => {
        position += 5;
        element.style.left = position + 'px';
    }, 16); // ~60fps
}

// Good: Using requestAnimationFrame
function animate() {
    const element = document.getElementById('box');
    let position = 0;

    function step() {
        position += 5;
        element.style.left = position + 'px';

        if (position < 500) {
            requestAnimationFrame(step);
        }
    }

    requestAnimationFrame(step);
}

// Better: With timing control
function animate() {
    const element = document.getElementById('box');
    let start = null;
    const duration = 2000; // 2 seconds
    const distance = 500;

    function step(timestamp) {
        if (!start) start = timestamp;

        const progress = timestamp - start;
        const percentage = Math.min(progress / duration, 1);

        element.style.left = (distance * percentage) + 'px';

        if (progress < duration) {
            requestAnimationFrame(step);
        }
    }

    requestAnimationFrame(step);
}
Warning: Avoid layout thrashing by batching reads and writes. Read all layout properties first, then write all changes.

6. Security Best Practices

XSS Prevention

Always sanitize user input:

// Bad: Direct insertion of user input
function displayMessage(message) {
    document.getElementById('output').innerHTML = message;
    // Vulnerable to XSS: <script>alert('hacked')</script>
}

// Good: Use textContent
function displayMessage(message) {
    document.getElementById('output').textContent = message;
    // Script tags rendered as text, not executed
}

// Good: Sanitize HTML
function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

function displayMessage(message) {
    const sanitized = escapeHtml(message);
    document.getElementById('output').innerHTML = sanitized;
}

// Better: Use a sanitization library
import DOMPurify from 'dompurify';

function displayMessage(message) {
    const clean = DOMPurify.sanitize(message);
    document.getElementById('output').innerHTML = clean;
}

Input Validation

Always validate and sanitize input:

// Input validation utilities
const Validator = {
    isEmail(email) {
        const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return re.test(email);
    },

    isUrl(url) {
        try {
            new URL(url);
            return true;
        } catch {
            return false;
        }
    },

    isNumeric(value) {
        return !isNaN(parseFloat(value)) && isFinite(value);
    },

    isInRange(value, min, max) {
        const num = Number(value);
        return num >= min && num <= max;
    },

    hasMinLength(str, length) {
        return str.length >= length;
    }
};

// Usage
function validateForm(data) {
    const errors = {};

    if (!Validator.isEmail(data.email)) {
        errors.email = 'Invalid email address';
    }

    if (!Validator.hasMinLength(data.password, 8)) {
        errors.password = 'Password must be at least 8 characters';
    }

    if (!Validator.isInRange(data.age, 18, 120)) {
        errors.age = 'Age must be between 18 and 120';
    }

    return {
        isValid: Object.keys(errors).length === 0,
        errors
    };
}

Content Security Policy

Implement CSP headers:

<!-- Add CSP meta tag -->
<meta http-equiv="Content-Security-Policy"
      content="default-src 'self';
               script-src 'self' https://trusted-cdn.com;
               style-src 'self' 'unsafe-inline';
               img-src 'self' data: https:;">

<!-- Or set in HTTP headers (preferred) -->
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com

<!-- Avoid inline scripts -->
<!-- Bad -->
<button onclick="doSomething()">Click</button>

<!-- Good -->
<button id="myButton">Click</button>
<script src="app.js"></script>

// app.js
document.getElementById('myButton').addEventListener('click', doSomething);

7. Accessibility in JavaScript

ARIA Attributes

Add ARIA attributes for screen readers:

// Managing ARIA attributes
function showNotification(message, type = 'info') {
    const notification = document.createElement('div');
    notification.className = `notification ${type}`;
    notification.textContent = message;

    // Add ARIA attributes
    notification.setAttribute('role', 'alert');
    notification.setAttribute('aria-live', 'polite');
    notification.setAttribute('aria-atomic', 'true');

    document.body.appendChild(notification);

    // Remove after delay
    setTimeout(() => {
        notification.remove();
    }, 5000);
}

// Toggle button state
function toggleButton(button) {
    const isPressed = button.getAttribute('aria-pressed') === 'true';
    button.setAttribute('aria-pressed', !isPressed);
    button.textContent = isPressed ? 'Show' : 'Hide';
}

// Expandable section
function toggleSection(button, sectionId) {
    const section = document.getElementById(sectionId);
    const isExpanded = button.getAttribute('aria-expanded') === 'true';

    button.setAttribute('aria-expanded', !isExpanded);
    section.hidden = isExpanded;
}

Focus Management

Manage focus for better keyboard navigation:

// Modal focus trap
class Modal {
    constructor(element) {
        this.element = element;
        this.focusableElements = null;
        this.firstFocusable = null;
        this.lastFocusable = null;
        this.previousFocus = null;
    }

    open() {
        // Save current focus
        this.previousFocus = document.activeElement;

        // Show modal
        this.element.style.display = 'block';
        this.element.setAttribute('aria-hidden', 'false');

        // Get focusable elements
        this.focusableElements = this.element.querySelectorAll(
            'a[href], button:not([disabled]), textarea, input, select'
        );

        this.firstFocusable = this.focusableElements[0];
        this.lastFocusable = this.focusableElements[this.focusableElements.length - 1];

        // Focus first element
        this.firstFocusable.focus();

        // Add event listeners
        this.element.addEventListener('keydown', this.handleKeydown.bind(this));
    }

    close() {
        // Hide modal
        this.element.style.display = 'none';
        this.element.setAttribute('aria-hidden', 'true');

        // Restore focus
        if (this.previousFocus) {
            this.previousFocus.focus();
        }

        // Remove event listeners
        this.element.removeEventListener('keydown', this.handleKeydown);
    }

    handleKeydown(e) {
        // Close on Escape
        if (e.key === 'Escape') {
            this.close();
            return;
        }

        // Trap focus with Tab
        if (e.key === 'Tab') {
            if (e.shiftKey) {
                // Shift + Tab
                if (document.activeElement === this.firstFocusable) {
                    e.preventDefault();
                    this.lastFocusable.focus();
                }
            } else {
                // Tab
                if (document.activeElement === this.lastFocusable) {
                    e.preventDefault();
                    this.firstFocusable.focus();
                }
            }
        }
    }
}

Keyboard Navigation

Implement keyboard support:

// Custom dropdown with keyboard support
class Dropdown {
    constructor(button, menu) {
        this.button = button;
        this.menu = menu;
        this.items = Array.from(menu.querySelectorAll('[role="menuitem"]'));
        this.currentIndex = -1;

        this.button.addEventListener('click', () => this.toggle());
        this.button.addEventListener('keydown', (e) => this.handleButtonKeydown(e));
        this.menu.addEventListener('keydown', (e) => this.handleMenuKeydown(e));
    }

    toggle() {
        const isOpen = this.menu.getAttribute('aria-hidden') === 'false';

        if (isOpen) {
            this.close();
        } else {
            this.open();
        }
    }

    open() {
        this.menu.setAttribute('aria-hidden', 'false');
        this.button.setAttribute('aria-expanded', 'true');
        this.items[0].focus();
        this.currentIndex = 0;
    }

    close() {
        this.menu.setAttribute('aria-hidden', 'true');
        this.button.setAttribute('aria-expanded', 'false');
        this.button.focus();
        this.currentIndex = -1;
    }

    handleButtonKeydown(e) {
        if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {
            e.preventDefault();
            this.open();
        }
    }

    handleMenuKeydown(e) {
        switch(e.key) {
            case 'Escape':
                e.preventDefault();
                this.close();
                break;

            case 'ArrowDown':
                e.preventDefault();
                this.currentIndex = (this.currentIndex + 1) % this.items.length;
                this.items[this.currentIndex].focus();
                break;

            case 'ArrowUp':
                e.preventDefault();
                this.currentIndex = this.currentIndex - 1;
                if (this.currentIndex < 0) {
                    this.currentIndex = this.items.length - 1;
                }
                this.items[this.currentIndex].focus();
                break;

            case 'Home':
                e.preventDefault();
                this.currentIndex = 0;
                this.items[0].focus();
                break;

            case 'End':
                e.preventDefault();
                this.currentIndex = this.items.length - 1;
                this.items[this.currentIndex].focus();
                break;

            case 'Enter':
            case ' ':
                e.preventDefault();
                this.items[this.currentIndex].click();
                this.close();
                break;
        }
    }
}

8. Testing Introduction

Unit Testing Basics

Write testable code and basic tests:

// Testable function
function calculateDiscount(price, discountPercent) {
    if (price < 0 || discountPercent < 0 || discountPercent > 100) {
        throw new Error('Invalid input');
    }

    return price * (discountPercent / 100);
}

// Simple test framework
function test(description, testFn) {
    try {
        testFn();
        console.log(`✓ ${description}`);
    } catch (error) {
        console.error(`✗ ${description}`);
        console.error(error.message);
    }
}

function assertEquals(actual, expected) {
    if (actual !== expected) {
        throw new Error(`Expected ${expected} but got ${actual}`);
    }
}

function assertThrows(fn, errorMessage) {
    try {
        fn();
        throw new Error('Expected function to throw');
    } catch (error) {
        if (!error.message.includes(errorMessage)) {
            throw new Error(`Expected error message to include "${errorMessage}"`);
        }
    }
}

// Run tests
test('calculates 10% discount correctly', () => {
    const result = calculateDiscount(100, 10);
    assertEquals(result, 10);
});

test('calculates 50% discount correctly', () => {
    const result = calculateDiscount(200, 50);
    assertEquals(result, 100);
});

test('throws error for negative price', () => {
    assertThrows(() => calculateDiscount(-100, 10), 'Invalid input');
});

test('throws error for invalid discount', () => {
    assertThrows(() => calculateDiscount(100, 150), 'Invalid input');
});

Test Structure

Organize tests with arrange-act-assert pattern:

// User class to test
class User {
    constructor(name, email) {
        this.name = name;
        this.email = email;
        this.isActive = false;
    }

    activate() {
        this.isActive = true;
    }

    deactivate() {
        this.isActive = false;
    }

    updateEmail(newEmail) {
        if (!newEmail.includes('@')) {
            throw new Error('Invalid email');
        }
        this.email = newEmail;
    }
}

// Tests using AAA pattern
test('User activation works correctly', () => {
    // Arrange
    const user = new User('John Doe', 'john@example.com');

    // Act
    user.activate();

    // Assert
    assertEquals(user.isActive, true);
});

test('User deactivation works correctly', () => {
    // Arrange
    const user = new User('Jane Doe', 'jane@example.com');
    user.activate();

    // Act
    user.deactivate();

    // Assert
    assertEquals(user.isActive, false);
});

test('Email update validates format', () => {
    // Arrange
    const user = new User('Bob Smith', 'bob@example.com');

    // Act & Assert
    assertThrows(
        () => user.updateEmail('invalid-email'),
        'Invalid email'
    );
});

test('Email update works with valid email', () => {
    // Arrange
    const user = new User('Alice Johnson', 'alice@example.com');
    const newEmail = 'alice.new@example.com';

    // Act
    user.updateEmail(newEmail);

    // Assert
    assertEquals(user.email, newEmail);
});
Tip: Popular testing frameworks include Jest, Mocha, Jasmine, and Vitest. They provide more features like mocking, async testing, and better reporting.

9. Code Review Checklist

Before Submitting Code

Review your code against this checklist:

// Code Quality Checklist
const codeReviewChecklist = {
    // Functionality
    functionality: [
        'Does the code work as intended?',
        'Are edge cases handled?',
        'Are error conditions handled?',
        'Is input validated?'
    ],

    // Code Style
    style: [
        'Are naming conventions followed?',
        'Is indentation consistent?',
        'Are comments clear and helpful?',
        'Is code properly formatted?'
    ],

    // Best Practices
    bestPractices: [
        'Are functions small and focused?',
        'Is code DRY (Don't Repeat Yourself)?',
        'Are constants used for magic numbers?',
        'Is const used by default?'
    ],

    // Performance
    performance: [
        'Are DOM manipulations batched?',
        'Are event listeners cleaned up?',
        'Are expensive operations cached?',
        'Is debouncing/throttling used where appropriate?'
    ],

    // Security
    security: [
        'Is user input sanitized?',
        'Are XSS vulnerabilities prevented?',
        'Is sensitive data protected?',
        'Are API keys kept secure?'
    ],

    // Accessibility
    accessibility: [
        'Are ARIA attributes used correctly?',
        'Is keyboard navigation supported?',
        'Are focus states visible?',
        'Are color contrasts sufficient?'
    ],

    // Testing
    testing: [
        'Are tests written and passing?',
        'Is test coverage adequate?',
        'Are edge cases tested?',
        'Are error conditions tested?'
    ],

    // Documentation
    documentation: [
        'Are complex sections commented?',
        'Is API documentation updated?',
        'Are README instructions clear?',
        'Are breaking changes noted?'
    ]
};

10. Staying Current with JavaScript

TC39 Process

Understanding how JavaScript evolves:

// TC39 Proposal Stages:
// Stage 0: Strawperson - Just an idea
// Stage 1: Proposal - Worth pursuing
// Stage 2: Draft - Precise syntax
// Stage 3: Candidate - Complete, awaiting implementation feedback
// Stage 4: Finished - Will be in next ECMAScript release

// Example: Recent additions
// Stage 4 (finalized):
// - Optional Chaining (?.)
const user = { profile: { name: 'John' } };
console.log(user?.profile?.name); // 'John'
console.log(user?.settings?.theme); // undefined (no error)

// - Nullish Coalescing (??)
const value = null ?? 'default'; // 'default'
const zero = 0 ?? 'default'; // 0 (not 'default')

// - Private Fields (#)
class BankAccount {
    #balance = 0; // Private field

    deposit(amount) {
        this.#balance += amount;
    }

    getBalance() {
        return this.#balance;
    }
}

// - Top-level await
// In modules:
const data = await fetch('/api/data').then(r => r.json());

// - Promise.allSettled()
const results = await Promise.allSettled([
    fetch('/api/1'),
    fetch('/api/2'),
    fetch('/api/3')
]);
// Returns all results, even if some fail

// Resources to stay updated:
// - https://github.com/tc39/proposals
// - https://developer.mozilla.org/en-US/
// - JavaScript Weekly newsletter
// - Twitter: @TC39
// - Can I Use (caniuse.com) for browser support

Comprehensive Best Practices Example

Here's a complete example following all best practices:

// taskManager.js - Complete example following best practices

// Constants
const TASK_STATUS = {
    PENDING: 'pending',
    IN_PROGRESS: 'in_progress',
    COMPLETED: 'completed'
};

const MAX_TASKS = 100;

// Custom error
class TaskError extends Error {
    constructor(message, code) {
        super(message);
        this.name = 'TaskError';
        this.code = code;
    }
}

// Validation utilities
const Validator = {
    isValidTitle(title) {
        return typeof title === 'string' && title.trim().length > 0;
    },

    isValidStatus(status) {
        return Object.values(TASK_STATUS).includes(status);
    }
};

// Task class with private fields
class Task {
    #id;
    #title;
    #status;
    #createdAt;

    constructor(title) {
        if (!Validator.isValidTitle(title)) {
            throw new TaskError('Invalid task title', 'INVALID_TITLE');
        }

        this.#id = crypto.randomUUID();
        this.#title = title;
        this.#status = TASK_STATUS.PENDING;
        this.#createdAt = new Date();
    }

    // Getters
    get id() { return this.#id; }
    get title() { return this.#title; }
    get status() { return this.#status; }
    get createdAt() { return this.#createdAt; }

    // Methods
    updateStatus(newStatus) {
        if (!Validator.isValidStatus(newStatus)) {
            throw new TaskError('Invalid status', 'INVALID_STATUS');
        }

        this.#status = newStatus;
    }

    toJSON() {
        return {
            id: this.#id,
            title: this.#title,
            status: this.#status,
            createdAt: this.#createdAt.toISOString()
        };
    }
}

// Task Manager class
class TaskManager {
    #tasks;
    #listeners;

    constructor() {
        this.#tasks = new Map();
        this.#listeners = new Set();
        this.#loadFromStorage();
    }

    // Public methods
    addTask(title) {
        if (this.#tasks.size >= MAX_TASKS) {
            throw new TaskError('Maximum tasks reached', 'MAX_TASKS');
        }

        try {
            const task = new Task(title);
            this.#tasks.set(task.id, task);
            this.#saveToStorage();
            this.#notifyListeners('taskAdded', task);

            return task;
        } catch (error) {
            console.error('Failed to add task:', error);
            throw error;
        }
    }

    removeTask(taskId) {
        const task = this.#tasks.get(taskId);

        if (!task) {
            throw new TaskError('Task not found', 'NOT_FOUND');
        }

        this.#tasks.delete(taskId);
        this.#saveToStorage();
        this.#notifyListeners('taskRemoved', task);
    }

    updateTaskStatus(taskId, status) {
        const task = this.#tasks.get(taskId);

        if (!task) {
            throw new TaskError('Task not found', 'NOT_FOUND');
        }

        task.updateStatus(status);
        this.#saveToStorage();
        this.#notifyListeners('taskUpdated', task);
    }

    getTasks() {
        return Array.from(this.#tasks.values());
    }

    getTasksByStatus(status) {
        return this.getTasks().filter(task => task.status === status);
    }

    // Event system
    on(event, callback) {
        this.#listeners.add({ event, callback });
    }

    off(event, callback) {
        this.#listeners.forEach(listener => {
            if (listener.event === event && listener.callback === callback) {
                this.#listeners.delete(listener);
            }
        });
    }

    // Private methods
    #notifyListeners(event, data) {
        this.#listeners.forEach(listener => {
            if (listener.event === event) {
                listener.callback(data);
            }
        });
    }

    #saveToStorage() {
        try {
            const data = {
                tasks: Array.from(this.#tasks.values()).map(task => task.toJSON())
            };
            localStorage.setItem('taskManager', JSON.stringify(data));
        } catch (error) {
            console.error('Failed to save to storage:', error);
        }
    }

    #loadFromStorage() {
        try {
            const data = localStorage.getItem('taskManager');

            if (data) {
                const parsed = JSON.parse(data);

                parsed.tasks.forEach(taskData => {
                    const task = new Task(taskData.title);
                    task.updateStatus(taskData.status);
                    this.#tasks.set(task.id, task);
                });
            }
        } catch (error) {
            console.error('Failed to load from storage:', error);
        }
    }
}

// UI Controller with accessibility
class TaskUI {
    constructor(taskManager, container) {
        this.taskManager = taskManager;
        this.container = container;

        this.#setupEventListeners();
        this.#render();
    }

    #setupEventListeners() {
        // Event delegation for task list
        this.container.addEventListener('click', this.#handleClick.bind(this));

        // Listen to task manager events
        this.taskManager.on('taskAdded', () => this.#render());
        this.taskManager.on('taskRemoved', () => this.#render());
        this.taskManager.on('taskUpdated', () => this.#render());
    }

    #handleClick(e) {
        const target = e.target;

        if (target.classList.contains('task-complete')) {
            const taskId = target.dataset.taskId;
            this.taskManager.updateTaskStatus(taskId, TASK_STATUS.COMPLETED);
        } else if (target.classList.contains('task-delete')) {
            const taskId = target.dataset.taskId;
            this.taskManager.removeTask(taskId);
        }
    }

    #render() {
        const tasks = this.taskManager.getTasks();

        // Use DocumentFragment for better performance
        const fragment = document.createDocumentFragment();

        tasks.forEach(task => {
            const item = this.#createTaskElement(task);
            fragment.appendChild(item);
        });

        // Clear and append
        this.container.innerHTML = '';
        this.container.appendChild(fragment);

        // Announce to screen readers
        this.#announceTaskCount(tasks.length);
    }

    #createTaskElement(task) {
        const li = document.createElement('li');
        li.className = `task-item task-${task.status}`;
        li.setAttribute('role', 'listitem');

        const checkbox = document.createElement('button');
        checkbox.className = 'task-complete';
        checkbox.dataset.taskId = task.id;
        checkbox.setAttribute('aria-label', `Mark ${task.title} as complete`);
        checkbox.textContent = task.status === TASK_STATUS.COMPLETED ? '✓' : '○';

        const title = document.createElement('span');
        title.className = 'task-title';
        title.textContent = task.title;

        const deleteBtn = document.createElement('button');
        deleteBtn.className = 'task-delete';
        deleteBtn.dataset.taskId = task.id;
        deleteBtn.setAttribute('aria-label', `Delete ${task.title}`);
        deleteBtn.textContent = '×';

        li.appendChild(checkbox);
        li.appendChild(title);
        li.appendChild(deleteBtn);

        return li;
    }

    #announceTaskCount(count) {
        const announcement = document.createElement('div');
        announcement.setAttribute('role', 'status');
        announcement.setAttribute('aria-live', 'polite');
        announcement.className = 'sr-only';
        announcement.textContent = `${count} tasks in list`;

        document.body.appendChild(announcement);

        setTimeout(() => announcement.remove(), 1000);
    }
}

// Initialize application
function initializeApp() {
    try {
        const taskManager = new TaskManager();
        const container = document.getElementById('task-list');
        const taskUI = new TaskUI(taskManager, container);

        // Add task form handler
        const form = document.getElementById('add-task-form');
        const input = document.getElementById('task-input');

        form.addEventListener('submit', (e) => {
            e.preventDefault();

            const title = input.value.trim();

            if (title) {
                try {
                    taskManager.addTask(title);
                    input.value = '';
                } catch (error) {
                    if (error instanceof TaskError) {
                        alert(error.message);
                    }
                }
            }
        });

        // Expose for debugging (remove in production)
        window.taskManager = taskManager;

    } catch (error) {
        console.error('Failed to initialize app:', error);
    }
}

// Run when DOM is ready
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initializeApp);
} else {
    initializeApp();
}

export { TaskManager, Task, TASK_STATUS };

Final Project Exercise

Create a complete application following all best practices:

  1. Build a notes application with categories
  2. Implement CRUD operations (Create, Read, Update, Delete)
  3. Add search and filter functionality
  4. Implement localStorage persistence
  5. Add keyboard shortcuts (Ctrl+N for new note, etc.)
  6. Make it fully accessible (ARIA, keyboard navigation)
  7. Optimize performance (debounce search, batch DOM updates)
  8. Add input validation and error handling
  9. Write unit tests for core functions
  10. Document your code with comments

Summary

Congratulations on completing the JavaScript Essentials course! You've learned:

  • Code Organization: Proper file structure, modules, and naming conventions
  • Variable Best Practices: Using const/let, meaningful names, avoiding globals
  • Function Best Practices: Single responsibility, pure functions, small focused functions
  • Error Handling: Consistent patterns, custom errors, proper logging
  • Performance Optimization: DOM manipulation, event delegation, debouncing, throttling
  • Security: XSS prevention, input validation, CSP
  • Accessibility: ARIA attributes, focus management, keyboard navigation
  • Testing: Unit test basics, test structure, assertions
  • Code Review: Quality checklist, review process
  • Staying Current: TC39 process, resources for learning
Next Steps: Continue learning by exploring modern frameworks (React, Vue, Angular), backend development with Node.js, TypeScript, and advanced patterns. Build real projects and contribute to open source!

Keep coding, keep learning, and always strive to write clean, maintainable, and efficient JavaScript code!

Tutorial Complete!

Congratulations! You have completed all lessons in this tutorial.