Skip to content

JavaScript Debugging

Debugging is a crucial part of software development for identifying, locating, and fixing errors. JavaScript provides various debugging tools and techniques to help developers understand code execution, inspect variable states, and analyze performance issues. Mastering debugging skills is essential for improving development efficiency and code quality.

What is Debugging

Debugging refers to the process of inspecting program state, tracing execution flow, and identifying and fixing errors during program runtime using various tools and techniques. Effective debugging helps developers:

  1. Understand code execution flow: Trace function calls and program execution paths
  2. Inspect variable states: View current values and changes of variables
  3. Identify error locations: Accurately locate where problems occur
  4. Analyze performance issues: Find performance bottlenecks in the program
  5. Verify logic correctness: Ensure the program executes as expected

Console Debugging Techniques

Basic console.log() Debugging

javascript
// Basic usage
const user = { name: "John", age: 25 };
console.log("User info:", user);

// Multiple parameters
console.log("Name:", user.name, "Age:", user.age);

// Formatted output
console.log("User %s is %d years old", user.name, user.age);

// Placeholder types
console.log("String: %s, Number: %d, Object: %o", "hello", 42, { a: 1 });

Other Console Methods

javascript
// Different log levels
console.debug("Debug info");
console.info("General info");
console.warn("Warning message");
console.error("Error message");

// Display data as table
const users = [
    { name: "John", age: 25, city: "New York" },
    { name: "Jane", age: 30, city: "Los Angeles" },
    { name: "Bob", age: 28, city: "Chicago" }
];
console.table(users);

// Grouped display
console.group("User Info");
console.log("Name: John");
console.log("Age: 25");
console.groupEnd();

// Timer
console.time("Data Processing");
// Simulate time-consuming operation
for (let i = 0; i < 1000000; i++) {
    // Some calculations
}
console.timeEnd("Data Processing");

// Assertion
const age = -5;
console.assert(age >= 0, "Age cannot be negative: %d", age);

Conditional Debugging

javascript
// Conditional output
const debugMode = true;
if (debugMode) {
    console.log("Debug mode: variable value is", someVariable);
}

// Using environment variables
const DEBUG = process.env.NODE_ENV === "development";
function debug(...args) {
    if (DEBUG) {
        console.log("[DEBUG]", ...args);
    }
}

debug("This is debug info");

Breakpoint Debugging

The debugger Statement

javascript
function calculateTotal(items) {
    let total = 0;
    
    debugger; // Program will pause here
    
    for (let item of items) {
        total += item.price * item.quantity;
    }
    
    return total;
}

const items = [
    { name: "Product 1", price: 100, quantity: 2 },
    { name: "Product 2", price: 50, quantity: 3 }
];

const total = calculateTotal(items);
console.log("Total:", total);

Conditional Breakpoints

javascript
function processUsers(users) {
    for (let i = 0; i < users.length; i++) {
        const user = users[i];
        
        // Only pause under specific conditions
        if (user.age > 30) {
            debugger;
        }
        
        // Process user
        user.processed = true;
    }
}

Browser Developer Tools

Elements Panel

javascript
// Inspect and modify DOM in console
document.querySelector("h1").style.color = "red";
document.querySelector("p").textContent = "Modified content";

// Inspect element
console.log(document.querySelector("#myElement"));

Console Panel Advanced Features

javascript
// Save variables for later use
const data = { users: [], products: [] };
// Can directly access data variable in console

// Use $_ to get the result of last expression
// 2 + 3  // Returns 5
// $_     // Returns 5

// Use $0, $1, $2, $3, $4 to access recently selected elements
// After selecting an element in Elements panel
// $0.textContent = "New content";

// Copy to clipboard
// copy({ name: "John", age: 25 }); // Copy object to clipboard

// Clear console
// clear(); // Or press Ctrl+L

Sources Panel

javascript
// Set breakpoints in Sources panel
function fibonacci(n) {
    if (n <= 1) return n;
    
    // Can set breakpoint on this line
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// Call function for debugging
console.log(fibonacci(10));

Network Panel

javascript
// Monitor network requests
fetch("https://api.example.com/users")
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error("Request failed:", error));

// Monitor XHR requests
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/users");
xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
        console.log("XHR response:", xhr.responseText);
    }
};
xhr.send();

Debugging Utility Functions

Variable Inspection Tool

javascript
// General debugging function
function debugVariable(name, value) {
    console.log(`[DEBUG] ${name}:`, value);
    console.log(`  Type: ${typeof value}`);
    console.log(`  Is Array: ${Array.isArray(value)}`);
    console.log(`  Is Null: ${value === null}`);
    console.log(`  Is Undefined: ${value === undefined}`);
    
    if (typeof value === "object" && value !== null) {
        console.log(`  Property Count: ${Object.keys(value).length}`);
        console.log(`  Prototype: ${Object.getPrototypeOf(value).constructor.name}`);
    }
}

// Usage example
const user = { name: "John", age: 25, hobbies: ["reading", "swimming"] };
debugVariable("user", user);

Function Execution Tracing

javascript
// Function execution tracing decorator
function traceFunction(fn, fnName) {
    return function(...args) {
        console.log(`[TRACE] Calling ${fnName}(${args.map(arg => JSON.stringify(arg)).join(", ")})`);
        const result = fn.apply(this, args);
        console.log(`[TRACE] ${fnName} returns:`, result);
        return result;
    };
}

// Usage example
function add(a, b) {
    return a + b;
}

const tracedAdd = traceFunction(add, "add");
console.log(tracedAdd(5, 3)); // 8

Performance Monitoring Tool

javascript
// Performance monitoring decorator
function performanceMonitor(fn, fnName) {
    return function(...args) {
        const start = performance.now();
        const result = fn.apply(this, args);
        const end = performance.now();
        console.log(`[PERF] ${fnName} execution time: ${(end - start).toFixed(2)}ms`);
        return result;
    };
}

// Usage example
function slowFunction() {
    // Simulate time-consuming operation
    for (let i = 0; i < 1000000; i++) {
        Math.random();
    }
    return "Done";
}

const monitoredFunction = performanceMonitor(slowFunction, "slowFunction");
monitoredFunction();

Async Debugging Techniques

Promise Debugging

javascript
// Debug Promise chain
function fetchUserData(userId) {
    console.log("[DEBUG] Starting to fetch user data, User ID:", userId);
    
    return fetch(`/api/users/${userId}`)
        .then(response => {
            console.log("[DEBUG] Received response, Status:", response.status);
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`);
            }
            return response.json();
        })
        .then(data => {
            console.log("[DEBUG] Parsed user data:", data);
            return data;
        })
        .catch(error => {
            console.error("[DEBUG] Failed to fetch user data:", error);
            throw error;
        });
}

// Debug with async/await
async function processUser(userId) {
    try {
        console.log("[DEBUG] Starting to process user:", userId);
        
        const user = await fetchUserData(userId);
        console.log("[DEBUG] Got user:", user);
        
        // Process user data
        const processedUser = {
            ...user,
            processedAt: new Date()
        };
        
        console.log("[DEBUG] Processed user:", processedUser);
        return processedUser;
    } catch (error) {
        console.error("[DEBUG] Failed to process user:", error);
        throw error;
    }
}

Event Debugging

javascript
// Event listener debugging
function addDebugEventListener(element, eventType, handler, debugName) {
    const debugHandler = function(event) {
        console.log(`[EVENT] ${debugName} - ${eventType} event triggered`);
        console.log(`  Event Target:`, event.target);
        console.log(`  Event Type:`, event.type);
        console.log(`  Timestamp:`, event.timeStamp);
        
        return handler.call(this, event);
    };
    
    element.addEventListener(eventType, debugHandler);
    return debugHandler;
}

// Usage example
const button = document.getElementById("myButton");
addDebugEventListener(button, "click", function() {
    console.log("Button clicked");
}, "Main Button");

Debug Configuration and Environment

Development Environment Debug Configuration

javascript
// Debug configuration management
class DebugConfig {
    static enabled = process.env.NODE_ENV === "development";
    static levels = {
        ERROR: 0,
        WARN: 1,
        INFO: 2,
        DEBUG: 3
    };
    
    static currentLevel = DebugConfig.levels.DEBUG;
    
    static log(level, ...args) {
        if (!this.enabled) return;
        if (this.levels[level] > this.currentLevel) return;
        
        const timestamp = new Date().toISOString();
        const prefix = `[${timestamp}] [${level}]`;
        
        switch (level) {
            case "ERROR":
                console.error(prefix, ...args);
                break;
            case "WARN":
                console.warn(prefix, ...args);
                break;
            case "INFO":
                console.info(prefix, ...args);
                break;
            case "DEBUG":
                console.log(prefix, ...args);
                break;
        }
    }
    
    static error(...args) { this.log("ERROR", ...args); }
    static warn(...args) { this.log("WARN", ...args); }
    static info(...args) { this.log("INFO", ...args); }
    static debug(...args) { this.log("DEBUG", ...args); }
}

// Usage example
DebugConfig.debug("Debug info");
DebugConfig.info("General info");
DebugConfig.warn("Warning info");
DebugConfig.error("Error info");

Conditional Debugging Tool

javascript
// Conditional debugging tool
class ConditionalDebugger {
    constructor(namespace) {
        this.namespace = namespace;
        this.enabled = this.isEnabled();
    }
    
    isEnabled() {
        // Check localStorage or environment variables
        const debugNamespaces = (typeof localStorage !== "undefined" 
            ? localStorage.getItem("debug") 
            : process.env.DEBUG) || "";
        
        return debugNamespaces.includes(this.namespace) || 
               debugNamespaces === "*" ||
               debugNamespaces.includes("*");
    }
    
    log(...args) {
        if (this.enabled) {
            console.log(`[${this.namespace}]`, ...args);
        }
    }
    
    trace(...args) {
        if (this.enabled) {
            console.trace(`[${this.namespace}]`, ...args);
        }
    }
    
    time(label) {
        if (this.enabled) {
            console.time(`[${this.namespace}] ${label}`);
        }
    }
    
    timeEnd(label) {
        if (this.enabled) {
            console.timeEnd(`[${this.namespace}] ${label}`);
        }
    }
}

// Usage example
const userDebugger = new ConditionalDebugger("user");
const apiDebugger = new ConditionalDebugger("api");

userDebugger.log("User logged in");
apiDebugger.time("API Request");
setTimeout(() => {
    apiDebugger.timeEnd("API Request");
}, 1000);

Debugging Best Practices

1. Structured Debug Information

javascript
// Structured debug logging
class StructuredLogger {
    static log(context, message, data = null) {
        const logEntry = {
            timestamp: new Date().toISOString(),
            context: context,
            message: message,
            data: data,
            userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "Node.js",
            url: typeof window !== "undefined" ? window.location.href : "Node.js"
        };
        
        console.log(JSON.stringify(logEntry, null, 2));
    }
    
    static error(context, error, additionalData = null) {
        const errorEntry = {
            timestamp: new Date().toISOString(),
            context: context,
            error: {
                message: error.message,
                name: error.name,
                stack: error.stack
            },
            additionalData: additionalData
        };
        
        console.error(JSON.stringify(errorEntry, null, 2));
    }
}

// Usage example
StructuredLogger.log("User Module", "User logged in successfully", {
    userId: 123,
    username: "John",
    loginTime: new Date()
});

try {
    throw new Error("Test error");
} catch (error) {
    StructuredLogger.error("User Module", error, {
        userId: 123,
        action: "login"
    });
}

2. Debug Code Cleanup

javascript
// Automatic cleanup of debug code in production
class DebugCleaner {
    static removeDebugCode(code) {
        // Remove console.log statements
        return code.replace(/console\.(log|debug|info|warn)\([^)]*\);?/g, "");
    }
    
    static stripDebugStatements() {
        if (process.env.NODE_ENV === "production") {
            // In production, debug code can be removed via build tools
            console.log = function() {};
            console.debug = function() {};
            console.info = function() {};
            console.warn = function() {};
        }
    }
}

// Call at application startup
DebugCleaner.stripDebugStatements();

3. Debug Tools Integration

javascript
// Debug tools collection
class DebugTools {
    // Variable watcher
    static watch(variable, name, callback) {
        const handler = {
            set(obj, prop, value) {
                console.log(`[WATCH] ${name}.${prop} changed from ${obj[prop]} to ${value}`);
                const result = Reflect.set(obj, prop, value);
                if (callback) callback(prop, obj[prop], value);
                return result;
            }
        };
        
        return new Proxy(variable, handler);
    }
    
    // Function call counter
    static countCalls(fn, name) {
        let count = 0;
        return function(...args) {
            count++;
            console.log(`[COUNT] ${name} called ${count} time(s)`);
            return fn.apply(this, args);
        };
    }
    
    // Memory usage monitoring
    static monitorMemory() {
        if (typeof performance !== "undefined" && performance.memory) {
            const memory = performance.memory;
            console.log("[MEMORY] Usage:");
            console.log(`  Used: ${(memory.usedJSHeapSize / 1048576).toFixed(2)} MB`);
            console.log(`  Total: ${(memory.totalJSHeapSize / 1048576).toFixed(2)} MB`);
            console.log(`  Limit: ${(memory.jsHeapSizeLimit / 1048576).toFixed(2)} MB`);
        }
    }
}

// Usage example
const user = { name: "John", age: 25 };
const watchedUser = DebugTools.watch(user, "user", (prop, oldValue, newValue) => {
    console.log(`User property ${prop} updated`);
});

watchedUser.age = 26; // Will trigger watcher

const countedFunction = DebugTools.countCalls(() => {
    console.log("Function executed");
}, "testFunction");

countedFunction(); // [COUNT] testFunction called 1 time(s)
countedFunction(); // [COUNT] testFunction called 2 time(s)

DebugTools.monitorMemory();

Practical Examples

Complete Debug System

javascript
// Complete debug system
class DebugSystem {
    constructor(options = {}) {
        this.enabled = options.enabled || false;
        this.namespace = options.namespace || "app";
        this.level = options.level || "debug";
        this.output = options.output || console;
        this.filters = options.filters || [];
        
        this.levels = {
            error: 0,
            warn: 1,
            info: 2,
            debug: 3
        };
    }
    
    shouldLog(level) {
        if (!this.enabled) return false;
        return this.levels[level] <= this.levels[this.level];
    }
    
    formatMessage(level, message, context = {}) {
        return {
            timestamp: new Date().toISOString(),
            level: level,
            namespace: this.namespace,
            message: message,
            context: context,
            userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
            url: typeof window !== "undefined" ? window.location.href : undefined
        };
    }
    
    log(level, message, context = {}) {
        if (!this.shouldLog(level)) return;
        
        const formattedMessage = this.formatMessage(level, message, context);
        
        switch (level) {
            case "error":
                this.output.error(JSON.stringify(formattedMessage, null, 2));
                break;
            case "warn":
                this.output.warn(JSON.stringify(formattedMessage, null, 2));
                break;
            case "info":
                this.output.info(JSON.stringify(formattedMessage, null, 2));
                break;
            case "debug":
                this.output.log(JSON.stringify(formattedMessage, null, 2));
                break;
        }
    }
    
    error(message, context = {}) { this.log("error", message, context); }
    warn(message, context = {}) { this.log("warn", message, context); }
    info(message, context = {}) { this.log("info", message, context); }
    debug(message, context = {}) { this.log("debug", message, context); }
    
    // Performance monitoring
    time(label, context = {}) {
        if (!this.shouldLog("debug")) return;
        
        const startTime = performance.now();
        this.debug(`Start timing: ${label}`, context);
        
        return {
            end: (endContext = {}) => {
                const endTime = performance.now();
                const duration = endTime - startTime;
                this.debug(`End timing: ${label}`, {
                    ...context,
                    ...endContext,
                    duration: `${duration.toFixed(2)}ms`
                });
                return duration;
            }
        };
    }
    
    // Function wrapper
    wrap(fn, name, context = {}) {
        return (...args) => {
            this.debug(`Calling function: ${name}`, {
                ...context,
                arguments: args
            });
            
            try {
                const result = fn(...args);
                this.debug(`Function returned: ${name}`, {
                    ...context,
                    result: result
                });
                return result;
            } catch (error) {
                this.error(`Function error: ${name}`, {
                    ...context,
                    error: error.message,
                    stack: error.stack
                });
                throw error;
            }
        };
    }
}

// Usage example
const debug = new DebugSystem({
    enabled: true,
    namespace: "user-service",
    level: "debug"
});

// Wrap function
const fetchUser = debug.wrap(async (userId) => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
}, "fetchUser");

// Performance monitoring
const timer = debug.time("User data processing");
setTimeout(() => {
    timer.end({ userId: 123 });
}, 100);

Frontend Component Debugger

javascript
// React/Vue component debug tool
class ComponentDebugger {
    constructor(componentName) {
        this.componentName = componentName;
        this.logs = [];
    }
    
    log(lifecycle, message, data = null) {
        const logEntry = {
            timestamp: Date.now(),
            lifecycle: lifecycle,
            message: message,
            data: data
        };
        
        this.logs.push(logEntry);
        console.log(`[${this.componentName}] [${lifecycle}] ${message}`, data);
    }
    
    // Lifecycle debugging
    componentDidMount() {
        this.log("mount", "Component mounted");
    }
    
    componentDidUpdate(prevProps, prevState) {
        this.log("update", "Component updated", { prevProps, prevState });
    }
    
    componentWillUnmount() {
        this.log("unmount", "Component will unmount");
    }
    
    // Render performance monitoring
    renderWithTiming(renderFunction) {
        const start = performance.now();
        const result = renderFunction();
        const end = performance.now();
        
        this.log("render", "Render completed", {
            duration: `${(end - start).toFixed(2)}ms`
        });
        
        return result;
    }
    
    // Get debug logs
    getLogs() {
        return this.logs;
    }
    
    // Clear logs
    clearLogs() {
        this.logs = [];
    }
}

Summary

Key points of JavaScript debugging:

  1. Console Debugging: console.log(), console.table(), console.time() and other methods
  2. Breakpoint Debugging: debugger statement, conditional breakpoints, browser developer tools
  3. Async Debugging: Promise chain debugging, async/await debugging, event debugging
  4. Debug Tools: Custom debug functions, performance monitoring, variable watching
  5. Debug Configuration: Environment differentiation, conditional debugging, debug level control
  6. Best Practices: Structured logging, debug code cleanup, tool integration
  7. Practical Applications: Complete application debug systems, component debug tools

Mastering debugging techniques is key to improving development efficiency and code quality. In the next chapter, we will learn about JavaScript JSON handling.

Content is for learning and research only.