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:
- Understand code execution flow: Trace function calls and program execution paths
- Inspect variable states: View current values and changes of variables
- Identify error locations: Accurately locate where problems occur
- Analyze performance issues: Find performance bottlenecks in the program
- Verify logic correctness: Ensure the program executes as expected
Console Debugging Techniques
Basic console.log() Debugging
// 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
// 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
// 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
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
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
// 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
// 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+LSources Panel
// 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
// 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
// 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
// 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)); // 8Performance Monitoring Tool
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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:
- Console Debugging: console.log(), console.table(), console.time() and other methods
- Breakpoint Debugging: debugger statement, conditional breakpoints, browser developer tools
- Async Debugging: Promise chain debugging, async/await debugging, event debugging
- Debug Tools: Custom debug functions, performance monitoring, variable watching
- Debug Configuration: Environment differentiation, conditional debugging, debug level control
- Best Practices: Structured logging, debug code cleanup, tool integration
- 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.