Advanced JavaScriptFeatured
JavaScript Debugging: Complete Developer Guide
Master debugging techniques in JavaScript. Learn browser DevTools, debugging strategies, error tracking, and performance profiling.
By JavaScriptDoc Team•
debuggingdevtoolsjavascriptperformancetroubleshooting
JavaScript Debugging: Complete Developer Guide
Debugging is an essential skill for JavaScript developers. This guide covers tools, techniques, and best practices for effectively debugging JavaScript applications.
Browser Developer Tools
Console Methods
// Basic console methods
console.log('Basic log message');
console.info('Information message');
console.warn('Warning message');
console.error('Error message');
// Formatting console output
console.log('Hello %s, you have %d points', 'John', 100);
console.log(
'%c Styled text',
'color: blue; font-size: 20px; font-weight: bold'
);
// Multiple values
console.log('Multiple', 'values', 'in', 'one', 'call');
// Object and array inspection
const user = { name: 'John', age: 30, hobbies: ['reading', 'gaming'] };
console.log(user);
console.dir(user); // Interactive object display
console.table(user); // Tabular display
// Grouping console output
console.group('User Details');
console.log('Name:', user.name);
console.log('Age:', user.age);
console.groupCollapsed('Hobbies');
user.hobbies.forEach((hobby) => console.log(hobby));
console.groupEnd();
console.groupEnd();
// Assertions
console.assert(user.age > 0, 'Age must be positive');
console.assert(user.name === 'Jane', 'Name is not Jane'); // Will show error
// Timing operations
console.time('Loop timer');
for (let i = 0; i < 1000000; i++) {
// Some operation
}
console.timeEnd('Loop timer');
// Counting
function processItem(item) {
console.count('processItem called');
// Process the item
}
// Stack traces
console.trace('Trace point');
// Clear console
// console.clear();
Advanced Console Techniques
// Custom console styling
const styles = [
'color: green',
'background: yellow',
'font-size: 30px',
'border: 1px solid red',
'text-shadow: 2px 2px black',
'padding: 10px',
].join(';');
console.log('%c Success! ', styles);
// Console profiling
console.profile('MyProfile');
// Code to profile
for (let i = 0; i < 1000; i++) {
document.createElement('div');
}
console.profileEnd('MyProfile');
// Memory usage
if (console.memory) {
console.log('Memory usage:', console.memory);
}
// Custom console wrapper
class Logger {
constructor(prefix = '') {
this.prefix = prefix;
this.enabled = true;
}
log(...args) {
if (this.enabled) {
console.log(`[${this.prefix}]`, ...args);
}
}
error(...args) {
if (this.enabled) {
console.error(`[${this.prefix}]`, ...args);
}
}
group(label) {
if (this.enabled) {
console.group(`[${this.prefix}] ${label}`);
}
}
groupEnd() {
if (this.enabled) {
console.groupEnd();
}
}
table(data) {
if (this.enabled) {
console.log(`[${this.prefix}]`);
console.table(data);
}
}
}
const logger = new Logger('MyApp');
logger.log('Application started');
Breakpoints and Debugger
Using the Debugger Statement
function calculateTotal(items) {
let total = 0;
for (const item of items) {
// Pause execution here when DevTools is open
debugger;
if (item.price && item.quantity) {
total += item.price * item.quantity;
}
}
return total;
}
// Conditional debugger
function processData(data) {
data.forEach((item, index) => {
// Only break for specific conditions
if (item.status === 'error') {
debugger;
}
// Process item
processItem(item);
});
}
// Debugger in production
function safeDebugger() {
// Only activate in development
if (process.env.NODE_ENV === 'development') {
debugger;
}
}
Breakpoint Types
// Line breakpoints - Set in DevTools on specific lines
// Conditional breakpoints
function updateUser(user) {
// Set conditional breakpoint: user.id === 123
user.lastUpdated = new Date();
saveUser(user);
}
// DOM breakpoints
// Set in DevTools Elements panel:
// - Subtree modifications
// - Attribute modifications
// - Node removal
// XHR/Fetch breakpoints
// Set in DevTools to break on specific URLs
fetch('/api/users')
.then((response) => response.json())
.then((data) => console.log(data));
// Event listener breakpoints
// Set in DevTools to break on specific events
document.addEventListener('click', function (event) {
console.log('Clicked:', event.target);
});
// Exception breakpoints
function riskyOperation() {
try {
// Code that might throw
JSON.parse('invalid json');
} catch (error) {
// DevTools can break on caught/uncaught exceptions
console.error('Error:', error);
}
}
Error Handling and Stack Traces
Enhanced Error Information
// Custom error classes
class ValidationError extends Error {
constructor(field, value, message) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.value = value;
this.timestamp = new Date();
// Capture stack trace
if (Error.captureStackTrace) {
Error.captureStackTrace(this, ValidationError);
}
}
toString() {
return `${this.name} in field '${this.field}': ${this.message}`;
}
}
// Error context tracking
class ErrorContext {
constructor() {
this.context = [];
}
push(info) {
this.context.push({
...info,
timestamp: new Date(),
});
}
pop() {
return this.context.pop();
}
captureError(error) {
return {
error,
context: [...this.context],
userAgent: navigator.userAgent,
url: window.location.href,
timestamp: new Date(),
};
}
}
const errorContext = new ErrorContext();
// Usage
function processUser(userId) {
errorContext.push({ operation: 'processUser', userId });
try {
// Operations that might fail
const user = fetchUser(userId);
validateUser(user);
saveUser(user);
} catch (error) {
const enhancedError = errorContext.captureError(error);
console.error('Enhanced error:', enhancedError);
throw error;
} finally {
errorContext.pop();
}
}
Stack Trace Analysis
// Parse stack traces
function parseStackTrace(error) {
const stack = error.stack || '';
const lines = stack.split('\n');
return lines
.slice(1)
.map((line) => {
const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
if (match) {
return {
function: match[1],
file: match[2],
line: parseInt(match[3]),
column: parseInt(match[4]),
};
}
return null;
})
.filter(Boolean);
}
// Async stack traces
async function asyncOperation() {
try {
await step1();
await step2();
await step3();
} catch (error) {
// Enhanced async stack trace in modern browsers
console.error('Async error:', error);
console.log('Stack:', parseStackTrace(error));
}
}
// Error boundary pattern
class ErrorBoundary {
constructor(handler) {
this.handler = handler;
this.setupGlobalHandlers();
}
setupGlobalHandlers() {
// Catch unhandled errors
window.addEventListener('error', (event) => {
this.handler({
type: 'error',
error: event.error,
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
});
});
// Catch unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
this.handler({
type: 'unhandledRejection',
reason: event.reason,
promise: event.promise,
});
});
}
wrap(fn) {
return (...args) => {
try {
const result = fn(...args);
if (result instanceof Promise) {
return result.catch((error) => {
this.handler({ type: 'promise', error });
throw error;
});
}
return result;
} catch (error) {
this.handler({ type: 'sync', error });
throw error;
}
};
}
}
Performance Debugging
Performance Profiling
// Performance marks and measures
performance.mark('myFunction-start');
// Function to profile
function expensiveOperation() {
// Simulate expensive operation
const arr = [];
for (let i = 0; i < 1000000; i++) {
arr.push(Math.random());
}
return arr.sort();
}
const result = expensiveOperation();
performance.mark('myFunction-end');
performance.measure('myFunction', 'myFunction-start', 'myFunction-end');
// Get performance entries
const measures = performance.getEntriesByName('myFunction');
console.log('Execution time:', measures[0].duration);
// Performance observer
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${entry.duration}ms`);
}
});
observer.observe({ entryTypes: ['measure'] });
// Memory profiling
class MemoryProfiler {
static snapshot() {
if (performance.memory) {
return {
usedJSHeapSize: performance.memory.usedJSHeapSize,
totalJSHeapSize: performance.memory.totalJSHeapSize,
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit,
};
}
return null;
}
static profile(fn, label = 'Memory Profile') {
const before = this.snapshot();
const startTime = performance.now();
const result = fn();
const endTime = performance.now();
const after = this.snapshot();
if (before && after) {
console.log(`${label}:`, {
duration: endTime - startTime,
memoryDelta: after.usedJSHeapSize - before.usedJSHeapSize,
before,
after,
});
}
return result;
}
}
Performance Monitoring
// FPS Monitor
class FPSMonitor {
constructor() {
this.fps = 0;
this.frames = 0;
this.lastTime = performance.now();
this.history = [];
this.maxHistory = 60;
}
start() {
this.update();
}
update() {
this.frames++;
const currentTime = performance.now();
const delta = currentTime - this.lastTime;
if (delta >= 1000) {
this.fps = Math.round((this.frames * 1000) / delta);
this.frames = 0;
this.lastTime = currentTime;
this.history.push(this.fps);
if (this.history.length > this.maxHistory) {
this.history.shift();
}
this.onUpdate(this.fps);
}
requestAnimationFrame(() => this.update());
}
onUpdate(fps) {
console.log(`FPS: ${fps}`);
}
getAverage() {
if (this.history.length === 0) return 0;
const sum = this.history.reduce((a, b) => a + b, 0);
return Math.round(sum / this.history.length);
}
}
// Network performance monitoring
class NetworkMonitor {
static getNetworkInfo() {
if ('connection' in navigator) {
const connection = navigator.connection;
return {
effectiveType: connection.effectiveType,
downlink: connection.downlink,
rtt: connection.rtt,
saveData: connection.saveData,
};
}
return null;
}
static measureFetch(url) {
const startTime = performance.now();
return fetch(url).then((response) => {
const endTime = performance.now();
const duration = endTime - startTime;
return {
response,
metrics: {
duration,
size: response.headers.get('content-length'),
type: response.headers.get('content-type'),
status: response.status,
},
};
});
}
}
Debugging Techniques
Binary Search Debugging
// Finding bugs using binary search
class BinaryDebugger {
static findBuggyElement(elements, testFunction) {
let left = 0;
let right = elements.length - 1;
let lastGood = -1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
const testElements = elements.slice(0, mid + 1);
if (testFunction(testElements)) {
// Bug not present, search right half
lastGood = mid;
left = mid + 1;
} else {
// Bug present, search left half
right = mid - 1;
}
}
return elements[lastGood + 1]; // First buggy element
}
static async findBuggyCommit(commits, testFunction) {
let left = 0;
let right = commits.length - 1;
while (left < right) {
const mid = Math.floor((left + right) / 2);
if (await testFunction(commits[mid])) {
left = mid + 1;
} else {
right = mid;
}
}
return commits[left];
}
}
State Debugging
// State snapshot and comparison
class StateDebugger {
constructor() {
this.snapshots = [];
this.maxSnapshots = 10;
}
capture(label, state) {
const snapshot = {
label,
timestamp: new Date(),
state: this.deepClone(state),
stackTrace: this.getStackTrace(),
};
this.snapshots.push(snapshot);
if (this.snapshots.length > this.maxSnapshots) {
this.snapshots.shift();
}
return snapshot;
}
deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
getStackTrace() {
const stack = new Error().stack;
return stack.split('\n').slice(3, 8).join('\n');
}
compare(index1, index2) {
const snap1 = this.snapshots[index1];
const snap2 = this.snapshots[index2];
if (!snap1 || !snap2) {
throw new Error('Invalid snapshot indices');
}
return this.diff(snap1.state, snap2.state);
}
diff(obj1, obj2, path = '') {
const differences = [];
// Check all keys in obj1
for (const key in obj1) {
const newPath = path ? `${path}.${key}` : key;
if (!(key in obj2)) {
differences.push({
path: newPath,
type: 'deleted',
oldValue: obj1[key],
});
} else if (typeof obj1[key] !== typeof obj2[key]) {
differences.push({
path: newPath,
type: 'changed',
oldValue: obj1[key],
newValue: obj2[key],
});
} else if (typeof obj1[key] === 'object' && obj1[key] !== null) {
differences.push(...this.diff(obj1[key], obj2[key], newPath));
} else if (obj1[key] !== obj2[key]) {
differences.push({
path: newPath,
type: 'changed',
oldValue: obj1[key],
newValue: obj2[key],
});
}
}
// Check for new keys in obj2
for (const key in obj2) {
if (!(key in obj1)) {
const newPath = path ? `${path}.${key}` : key;
differences.push({ path: newPath, type: 'added', newValue: obj2[key] });
}
}
return differences;
}
replay() {
this.snapshots.forEach((snapshot, index) => {
console.group(`Snapshot ${index}: ${snapshot.label}`);
console.log('Time:', snapshot.timestamp);
console.log('State:', snapshot.state);
console.log('Stack:', snapshot.stackTrace);
console.groupEnd();
});
}
}
Remote Debugging
// Remote debugging helper
class RemoteDebugger {
constructor(endpoint) {
this.endpoint = endpoint;
this.sessionId = this.generateSessionId();
this.buffer = [];
this.maxBufferSize = 100;
this.flushInterval = 5000;
this.startFlushTimer();
this.interceptConsole();
}
generateSessionId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
interceptConsole() {
const methods = ['log', 'warn', 'error', 'info'];
methods.forEach((method) => {
const original = console[method];
console[method] = (...args) => {
original.apply(console, args);
this.capture(method, args);
};
});
}
capture(level, args) {
const entry = {
level,
message: args.map((arg) => this.serialize(arg)).join(' '),
timestamp: new Date().toISOString(),
url: window.location.href,
userAgent: navigator.userAgent,
stack: new Error().stack,
};
this.buffer.push(entry);
if (this.buffer.length >= this.maxBufferSize) {
this.flush();
}
}
serialize(obj) {
try {
if (typeof obj === 'object') {
return JSON.stringify(obj, null, 2);
}
return String(obj);
} catch (e) {
return '[Unserializable]';
}
}
async flush() {
if (this.buffer.length === 0) return;
const data = {
sessionId: this.sessionId,
entries: [...this.buffer],
};
this.buffer = [];
try {
await fetch(this.endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
} catch (error) {
console.error('Failed to send debug data:', error);
}
}
startFlushTimer() {
setInterval(() => this.flush(), this.flushInterval);
}
}
Visual Debugging
// DOM element highlighting
class VisualDebugger {
static highlight(element, color = 'red', duration = 2000) {
const originalBorder = element.style.border;
element.style.border = `3px solid ${color}`;
setTimeout(() => {
element.style.border = originalBorder;
}, duration);
}
static showBoundingBox(element) {
const rect = element.getBoundingClientRect();
const box = document.createElement('div');
Object.assign(box.style, {
position: 'fixed',
top: rect.top + 'px',
left: rect.left + 'px',
width: rect.width + 'px',
height: rect.height + 'px',
border: '2px solid red',
backgroundColor: 'rgba(255, 0, 0, 0.1)',
pointerEvents: 'none',
zIndex: 9999,
});
document.body.appendChild(box);
setTimeout(() => box.remove(), 3000);
}
static visualizeEventFlow(element) {
const events = ['click', 'mouseenter', 'mouseleave', 'focus', 'blur'];
const log = document.createElement('div');
Object.assign(log.style, {
position: 'fixed',
top: '10px',
right: '10px',
width: '300px',
maxHeight: '400px',
overflow: 'auto',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
color: 'white',
padding: '10px',
fontSize: '12px',
fontFamily: 'monospace',
zIndex: 10000,
});
document.body.appendChild(log);
events.forEach((eventType) => {
element.addEventListener(
eventType,
function (event) {
const entry = document.createElement('div');
entry.textContent = `${new Date().toLocaleTimeString()}: ${eventType} on ${event.target.tagName}`;
log.appendChild(entry);
log.scrollTop = log.scrollHeight;
},
true
);
});
return () => log.remove();
}
}
// Layout debugging
class LayoutDebugger {
static showAllBorders() {
const style = document.createElement('style');
style.textContent = `
* { outline: 1px solid red !important; }
*:before { outline: 1px solid green !important; }
*:after { outline: 1px solid blue !important; }
`;
document.head.appendChild(style);
return () => style.remove();
}
static showGridOverlay() {
const overlay = document.createElement('div');
Object.assign(overlay.style, {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundImage: `
repeating-linear-gradient(
0deg,
rgba(0, 0, 0, 0.1) 0px,
transparent 1px,
transparent 10px,
rgba(0, 0, 0, 0.1) 11px
),
repeating-linear-gradient(
90deg,
rgba(0, 0, 0, 0.1) 0px,
transparent 1px,
transparent 10px,
rgba(0, 0, 0, 0.1) 11px
)
`,
pointerEvents: 'none',
zIndex: 9998,
});
document.body.appendChild(overlay);
return () => overlay.remove();
}
}
Testing and Debugging Integration
// Debug test helper
class TestDebugger {
static async runWithDebug(testFn, options = {}) {
const debug = {
logs: [],
errors: [],
snapshots: [],
performance: [],
};
// Intercept console
const originalLog = console.log;
const originalError = console.error;
console.log = (...args) => {
debug.logs.push({ timestamp: Date.now(), args });
if (options.showLogs) originalLog(...args);
};
console.error = (...args) => {
debug.errors.push({ timestamp: Date.now(), args });
if (options.showErrors) originalError(...args);
};
// Run test
const startTime = performance.now();
let result;
let error;
try {
result = await testFn();
} catch (e) {
error = e;
}
const endTime = performance.now();
// Restore console
console.log = originalLog;
console.error = originalError;
// Create report
const report = {
success: !error,
result,
error,
duration: endTime - startTime,
debug,
};
if (options.verbose) {
this.printReport(report);
}
return report;
}
static printReport(report) {
console.group('Test Debug Report');
console.log('Success:', report.success);
console.log('Duration:', report.duration + 'ms');
if (report.error) {
console.group('Error');
console.error(report.error);
console.groupEnd();
}
if (report.debug.logs.length > 0) {
console.group('Logs');
report.debug.logs.forEach((log) => {
console.log(...log.args);
});
console.groupEnd();
}
console.groupEnd();
}
}
Best Practices
-
Use appropriate debugging tools
// Development vs Production const debug = process.env.NODE_ENV === 'development' ? console.log : () => {};
-
Clean up debug code
// Use debug flags const DEBUG = false; if (DEBUG) { console.log('Debug info'); }
-
Structured logging
function log(level, message, data = {}) { console.log( JSON.stringify({ level, message, data, timestamp: new Date().toISOString(), }) ); }
-
Preserve error context
try { riskyOperation(); } catch (error) { error.context = { user: currentUser, action: 'riskyOperation' }; throw error; }
Conclusion
Effective debugging is crucial for JavaScript development:
- Browser DevTools for interactive debugging
- Console methods for logging and inspection
- Breakpoints for step-by-step execution
- Performance profiling for optimization
- Error tracking for production issues
- Visual debugging for UI problems
Key takeaways:
- Master browser developer tools
- Use appropriate debugging techniques
- Implement proper error handling
- Monitor performance metrics
- Clean up debug code before production
- Document debugging findings
Master debugging to efficiently solve problems and build robust applications!