Modern JavaScript Development: ES6+, TypeScript, and Best Practices Guide

Introduction

JavaScript has evolved significantly from its jQuery-dominated era to a mature, feature-rich language with powerful ES6+ features, TypeScript support, and modern frameworks. This comprehensive guide covers modern JavaScript development practices, patterns, and techniques for building performant, maintainable applications.

Modern JavaScript Fundamentals

ES6+ Variable Declarations

Replace var with modern alternatives:

// Modern approach - use const by default
const API_URL = "https://api.example.com";
const users = [];

// Use let for variables that will be reassigned
let currentUser = null;
let counter = 0;

// Avoid var entirely
// var outdated = true; // ❌ Don't use

Arrow Functions and Modern Function Syntax

// Traditional function vs Arrow function
function traditionalFunction(name) {
    return `Hello, ${name}!`;
}

// Modern arrow function
const modernFunction = (name) => `Hello, ${name}!`;

// Async arrow functions
const fetchUserData = async (userId) => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
};

// Array methods with arrow functions
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((n) => n * 2);
const evens = numbers.filter((n) => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);

Template Literals and String Interpolation

// Modern string formatting
const name = "John";
const age = 30;

// Template literals (preferred)
const message = `Hello, ${name}! You are ${age} years old.`;

// Multi-line strings
const htmlTemplate = `
    <div class="user-card">
        <h2>${name}</h2>
        <p>Age: ${age}</p>
    </div>
`;

// Tagged template literals for advanced formatting
const currency = (strings, ...values) => {
    return strings.reduce((result, string, i) => {
        const value = values[i] ? `$${values[i].toFixed(2)}` : "";
        return result + string + value;
    }, "");
};

const price = 19.99;
const formatted = currency`The price is ${price}`; // "The price is $19.99"

Destructuring and Spread Operator

// Object destructuring
const user = { name: "John", email: "[email protected]", age: 30 };
const { name, email } = user;
const { age, country = "Unknown" } = user; // Default values

// Array destructuring
const colors = ["red", "green", "blue"];
const [primary, secondary, tertiary] = colors;
const [first, ...rest] = colors; // Rest operator

// Function parameter destructuring
const createUser = ({ name, email, age = 18 }) => {
    return { name, email, age, createdAt: new Date() };
};

// Spread operator for arrays and objects
const originalArray = [1, 2, 3];
const extendedArray = [...originalArray, 4, 5, 6];

const originalObject = { name: "John", age: 30 };
const extendedObject = { ...originalObject, email: "[email protected]" };

Modern DOM Manipulation

Vanilla JavaScript DOM Methods

Replace jQuery selectors with modern DOM APIs:

// Modern DOM selection
const element = document.querySelector(".my-class");
const elements = document.querySelectorAll(".my-class");
const byId = document.getElementById("my-id");

// Class manipulation
element.classList.add("active");
element.classList.remove("inactive");
element.classList.toggle("visible");
element.classList.contains("active"); // returns boolean

// Modern event handling
element.addEventListener("click", (event) => {
    event.preventDefault();
    console.log("Element clicked");
});

// Event delegation (modern alternative to jQuery's on())
document.addEventListener("click", (event) => {
    if (event.target.matches(".dynamic-button")) {
        handleDynamicButtonClick(event);
    }
});

// Modern attribute manipulation
element.setAttribute("data-value", "123");
const value = element.getAttribute("data-value");
element.dataset.value = "123"; // Modern data attribute access
const dataValue = element.dataset.value;

Element Creation and Manipulation

// Creating elements
const createCard = (user) => {
    const card = document.createElement("div");
    card.className = "user-card";

    card.innerHTML = `
        <h3>${user.name}</h3>
        <p>${user.email}</p>
        <button class="contact-btn" data-user-id="${user.id}">
            Contact
        </button>
    `;

    return card;
};

// Modern insertion methods
const container = document.querySelector(".container");
const newElement = createCard(userData);

// Insert methods
container.appendChild(newElement);
container.insertAdjacentHTML("beforeend", htmlString);
container.append(element1, element2); // Can append multiple elements
container.prepend(element); // Insert at beginning

// Replace jQuery's .html() and .text()
element.innerHTML = "<p>HTML content</p>";
element.textContent = "Plain text content";

Asynchronous JavaScript

Promises and Async/Await

// Modern promise-based API calls
const fetchUserData = async (userId) => {
    try {
        const response = await fetch(`/api/users/${userId}`);

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

        const userData = await response.json();
        return userData;
    } catch (error) {
        console.error("Failed to fetch user data:", error);
        throw error;
    }
};

// Promise chaining (when async/await isn't suitable)
const processUserData = (userId) => {
    return fetchUserData(userId)
        .then((user) => enrichUserData(user))
        .then((enrichedUser) => saveUserData(enrichedUser))
        .catch((error) => handleUserError(error));
};

// Parallel execution
const loadDashboardData = async () => {
    try {
        const [users, posts, comments] = await Promise.all([
            fetch("/api/users").then((r) => r.json()),
            fetch("/api/posts").then((r) => r.json()),
            fetch("/api/comments").then((r) => r.json()),
        ]);

        return { users, posts, comments };
    } catch (error) {
        console.error("Failed to load dashboard data:", error);
    }
};

Modern Animation Techniques

Replace jQuery animations with modern approaches:

// CSS-based animations with JavaScript triggers
const animateElement = (element, animationClass, duration = 300) => {
    return new Promise((resolve) => {
        element.classList.add(animationClass);

        setTimeout(() => {
            element.classList.remove(animationClass);
            resolve();
        }, duration);
    });
};

// Web Animations API
const slideInAnimation = (element) => {
    return element.animate(
        [
            { transform: "translateX(-100%)", opacity: 0 },
            { transform: "translateX(0)", opacity: 1 },
        ],
        {
            duration: 300,
            easing: "ease-out",
            fill: "forwards",
        }
    );
};

// Modern scroll animations using Intersection Observer
const createScrollObserver = (callback, options = {}) => {
    const defaultOptions = {
        threshold: 0.1,
        rootMargin: "0px 0px -50px 0px",
    };

    const observer = new IntersectionObserver(callback, {
        ...defaultOptions,
        ...options,
    });
    return observer;
};

// Usage example
const animateOnScroll = createScrollObserver((entries) => {
    entries.forEach((entry) => {
        if (entry.isIntersecting) {
            entry.target.classList.add("animate-in");
        }
    });
});

document.querySelectorAll(".animate-on-scroll").forEach((el) => {
    animateOnScroll.observe(el);
});

Form Handling and Validation

Modern Form Processing

// Modern form handling with FormData
const handleFormSubmission = async (form) => {
    const formData = new FormData(form);

    // Convert to object if needed
    const data = Object.fromEntries(formData.entries());

    // Add validation
    const errors = validateFormData(data);
    if (errors.length > 0) {
        displayErrors(errors);
        return;
    }

    try {
        const response = await fetch(form.action, {
            method: form.method,
            body: formData,
        });

        if (response.ok) {
            showSuccessMessage();
            form.reset();
        } else {
            throw new Error("Form submission failed");
        }
    } catch (error) {
        showErrorMessage(error.message);
    }
};

// Modern form validation
const validateFormData = (data) => {
    const errors = [];

    if (!data.email?.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
        errors.push("Please enter a valid email address");
    }

    if (!data.name?.trim()) {
        errors.push("Name is required");
    }

    return errors;
};

// Event delegation for dynamic forms
document.addEventListener("submit", (event) => {
    if (event.target.matches(".ajax-form")) {
        event.preventDefault();
        handleFormSubmission(event.target);
    }
});

Modern JavaScript Patterns

Module Pattern and ES6 Modules

// ES6 Module exports
// utils.js
export const formatCurrency = (amount, currency = "USD") => {
    return new Intl.NumberFormat("en-US", {
        style: "currency",
        currency: currency,
    }).format(amount);
};

export const debounce = (func, delay) => {
    let timeoutId;
    return (...args) => {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => func.apply(null, args), delay);
    };
};

export default class ApiClient {
    constructor(baseURL) {
        this.baseURL = baseURL;
    }

    async get(endpoint) {
        const response = await fetch(`${this.baseURL}${endpoint}`);
        return response.json();
    }
}

// main.js - ES6 Module imports
import ApiClient, { formatCurrency, debounce } from "./utils.js";

const api = new ApiClient("https://api.example.com");
const debouncedSearch = debounce(performSearch, 300);

Class-based Components

class ComponentBase {
    constructor(element, options = {}) {
        this.element = element;
        this.options = { ...this.defaultOptions, ...options };
        this.init();
    }

    get defaultOptions() {
        return {};
    }

    init() {
        this.bindEvents();
        this.render();
    }

    bindEvents() {
        // Override in subclasses
    }

    render() {
        // Override in subclasses
    }

    destroy() {
        // Cleanup logic
    }
}

class Modal extends ComponentBase {
    get defaultOptions() {
        return {
            backdrop: true,
            keyboard: true,
            autoFocus: true,
        };
    }

    bindEvents() {
        if (this.options.keyboard) {
            document.addEventListener("keydown", this.handleKeydown.bind(this));
        }

        this.element
            .querySelector(".close-btn")
            ?.addEventListener("click", () => {
                this.hide();
            });
    }

    show() {
        this.element.classList.add("active");
        document.body.classList.add("modal-open");

        if (this.options.autoFocus) {
            this.element.focus();
        }
    }

    hide() {
        this.element.classList.remove("active");
        document.body.classList.remove("modal-open");
    }

    handleKeydown(event) {
        if (event.key === "Escape") {
            this.hide();
        }
    }
}

// Usage
const modal = new Modal(document.querySelector("#my-modal"), {
    backdrop: false,
});

TypeScript Integration

Basic TypeScript Setup

// types.ts
interface User {
    id: number;
    name: string;
    email: string;
    avatar?: string;
}

interface ApiResponse<T> {
    data: T;
    status: "success" | "error";
    message?: string;
}

// api.ts
class TypedApiClient {
    private baseURL: string;

    constructor(baseURL: string) {
        this.baseURL = baseURL;
    }

    async get<T>(endpoint: string): Promise<ApiResponse<T>> {
        try {
            const response = await fetch(`${this.baseURL}${endpoint}`);
            const data = await response.json();
            return data;
        } catch (error) {
            return {
                data: null as T,
                status: "error",
                message:
                    error instanceof Error ? error.message : "Unknown error",
            };
        }
    }

    async post<T>(endpoint: string, body: unknown): Promise<ApiResponse<T>> {
        try {
            const response = await fetch(`${this.baseURL}${endpoint}`, {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                },
                body: JSON.stringify(body),
            });

            const data = await response.json();
            return data;
        } catch (error) {
            return {
                data: null as T,
                status: "error",
                message:
                    error instanceof Error ? error.message : "Unknown error",
            };
        }
    }
}

// Usage with type safety
const api = new TypedApiClient("https://api.example.com");

const loadUsers = async (): Promise<User[]> => {
    const response = await api.get<User[]>("/users");

    if (response.status === "success") {
        return response.data;
    } else {
        throw new Error(response.message || "Failed to load users");
    }
};

Performance Optimization

Lazy Loading and Code Splitting

// Dynamic imports for code splitting
const loadChartLibrary = async () => {
    const { Chart } = await import("./chart-library.js");
    return Chart;
};

// Lazy load components when needed
const initializeChart = async (element, data) => {
    const Chart = await loadChartLibrary();
    return new Chart(element, data);
};

// Image lazy loading with Intersection Observer
const createImageLazyLoader = () => {
    const imageObserver = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
            if (entry.isIntersecting) {
                const img = entry.target;
                img.src = img.dataset.src;
                img.classList.remove("lazy");
                imageObserver.unobserve(img);
            }
        });
    });

    document.querySelectorAll("img[data-src]").forEach((img) => {
        imageObserver.observe(img);
    });
};

// Memory leak prevention
const cleanupFunction = () => {
    // Remove event listeners
    document.removeEventListener("scroll", scrollHandler);

    // Clear intervals/timeouts
    clearInterval(intervalId);
    clearTimeout(timeoutId);

    // Disconnect observers
    observer.disconnect();
};

Efficient DOM Updates

// Use DocumentFragment for multiple DOM insertions
const createUserList = (users) => {
    const fragment = document.createDocumentFragment();

    users.forEach((user) => {
        const userElement = createUserElement(user);
        fragment.appendChild(userElement);
    });

    return fragment;
};

// Batch DOM updates
const updateMultipleElements = (updates) => {
    // Use requestAnimationFrame for smooth updates
    requestAnimationFrame(() => {
        updates.forEach(({ element, properties }) => {
            Object.assign(element.style, properties);
        });
    });
};

// Virtual scrolling for large lists
class VirtualList {
    constructor(container, items, itemHeight) {
        this.container = container;
        this.items = items;
        this.itemHeight = itemHeight;
        this.visibleStart = 0;
        this.visibleEnd = 0;

        this.init();
    }

    init() {
        this.container.style.height = `${
            this.items.length * this.itemHeight
        }px`;
        this.container.addEventListener("scroll", this.handleScroll.bind(this));
        this.render();
    }

    handleScroll() {
        const scrollTop = this.container.scrollTop;
        const containerHeight = this.container.clientHeight;

        this.visibleStart = Math.floor(scrollTop / this.itemHeight);
        this.visibleEnd = Math.min(
            this.visibleStart +
                Math.ceil(containerHeight / this.itemHeight) +
                1,
            this.items.length
        );

        this.render();
    }

    render() {
        const visibleItems = this.items.slice(
            this.visibleStart,
            this.visibleEnd
        );

        this.container.innerHTML = "";

        visibleItems.forEach((item, index) => {
            const element = this.createItemElement(item);
            element.style.position = "absolute";
            element.style.top = `${
                (this.visibleStart + index) * this.itemHeight
            }px`;
            this.container.appendChild(element);
        });
    }

    createItemElement(item) {
        const element = document.createElement("div");
        element.className = "list-item";
        element.textContent = item.name;
        return element;
    }
}

Error Handling and Debugging

Modern Error Handling Patterns

// Custom error classes
class ApiError extends Error {
    constructor(message, statusCode, response) {
        super(message);
        this.name = "ApiError";
        this.statusCode = statusCode;
        this.response = response;
    }
}

class ValidationError extends Error {
    constructor(message, field) {
        super(message);
        this.name = "ValidationError";
        this.field = field;
    }
}

// Global error handling
window.addEventListener("error", (event) => {
    console.error("Global error:", event.error);
    // Send to error reporting service
    reportError(event.error);
});

window.addEventListener("unhandledrejection", (event) => {
    console.error("Unhandled promise rejection:", event.reason);
    reportError(event.reason);
});

// Async error handling with proper propagation
const safeAsyncOperation = async (operation, fallback = null) => {
    try {
        return await operation();
    } catch (error) {
        console.error("Operation failed:", error);

        if (error instanceof ApiError && error.statusCode === 401) {
            // Handle authentication errors
            redirectToLogin();
        } else if (error instanceof ValidationError) {
            // Handle validation errors
            showValidationError(error.field, error.message);
        }

        return fallback;
    }
};

Testing Modern JavaScript

Unit Testing with Modern Approaches

// Modern testing patterns
const createMockApiClient = () => ({
    get: jest.fn(),
    post: jest.fn(),
    put: jest.fn(),
    delete: jest.fn(),
});

// Test async functions
describe("User service", () => {
    it("should load user data successfully", async () => {
        const mockApi = createMockApiClient();
        mockApi.get.mockResolvedValue({
            data: { id: 1, name: "John" },
            status: "success",
        });

        const userService = new UserService(mockApi);
        const user = await userService.loadUser(1);

        expect(user).toEqual({ id: 1, name: "John" });
        expect(mockApi.get).toHaveBeenCalledWith("/users/1");
    });

    it("should handle API errors gracefully", async () => {
        const mockApi = createMockApiClient();
        mockApi.get.mockRejectedValue(new Error("Network error"));

        const userService = new UserService(mockApi);

        await expect(userService.loadUser(1)).rejects.toThrow("Network error");
    });
});

Best Practices Summary

Code Organization

  • Use ES6 modules for code organization
  • Implement proper separation of concerns
  • Use TypeScript for large applications
  • Follow consistent naming conventions

Performance

  • Minimize DOM manipulation
  • Use event delegation for dynamic content
  • Implement lazy loading for resources
  • Avoid memory leaks with proper cleanup

Error Handling

  • Implement comprehensive error boundaries
  • Use custom error types for better debugging
  • Provide meaningful error messages to users
  • Log errors for monitoring and debugging

Security

  • Validate and sanitize all user inputs
  • Use HTTPS for all API communications
  • Implement proper authentication and authorization
  • Avoid storing sensitive data in localStorage

Migration from jQuery

Common jQuery to Vanilla JS Conversions

// jQuery -> Modern JavaScript
// $(document).ready() -> DOMContentLoaded
// jQuery
$(document).ready(function () {
    // code here
});

// Modern JavaScript
document.addEventListener("DOMContentLoaded", () => {
    // code here
});

// Element selection and manipulation
// jQuery: $('.class').addClass('active')
// Modern: document.querySelectorAll('.class').forEach(el => el.classList.add('active'))

// AJAX requests
// jQuery: $.ajax(), $.get(), $.post()
// Modern: fetch() API with async/await

// Animation
// jQuery: .fadeIn(), .slideUp(), .animate()
// Modern: CSS transitions/animations + classList, Web Animations API

This guide provides a solid foundation for modern JavaScript development. The key is to embrace modern language features, prioritize performance, and maintain clean, testable code while moving away from older patterns and dependencies.