Module 74 : Debugging Techniques and DevTools .
Debugging is a scientific process: form a hypothesis, design a test, observe, refine.
Steps to follow:
Reproduce: Get a reliable, minimal repro. Use deterministic inputs and disable unrelated features.
Observe: Use logs, breakpoints, and browser tools to inspect state, network, and events.
Hypothesize: Based on observation, propose one or more causes.
Isolate: Reduce code to the smallest case that still demonstrates the bug.
Fix: Apply the minimal change that solves the issue.
Verify & Prevent: Add tests, add logging, or add assertions to avoid regressions.
Common traps: assuming state, ignoring asynchronous ordering, over-logging (noise), not using source maps for transpiled code.
2. DevTools Anatomy
Console — interactive REPL, console.* helpers, live expressions.
Sources (Debugger) — view files, set breakpoints, step controls, watch expressions, scope inspection, call stack.
Network — inspect requests, status codes, headers, request/response bodies, WebSockets, throttling.
Performance — CPU profiling, flame charts, timeline recording of frames, paint/layout events.
Memory — heap snapshots, allocation instrumentation, detect detached DOM nodes and retained objects.
Application — storage (local/session/cookies), service workers, manifest, caches.
Security — mixed content, CORS errors, CSP violation reports.
Tip: open DevTools (F12 or Ctrl/Cmd+Shift+I) and dock to the side to follow along.
3.Debugging & Tricks
methods and patterns
console.log(...) – basic, but use sparingly.
console.info, console.warn, console.error – semantic levels.
console.table(arrayOrObject) – tabular display for arrays/objects.
console.group()/console.groupEnd() – structured logs.
console.assert(condition, message) – only logs when condition is false.
console.trace() – prints the current stack trace.
console.count(label) – counts occurrences.
console.time(label) / console.timeEnd(label) – simple timing.
Logpoints (in Chrome DevTools) – set log messages at a line without changing source files.
Pattern: log once with context
console.log('fetchUser result', { id, responseLength: data.length, time: Date.now() });
Avoid logging raw large objects repeatedly; snapshot key fields.
Shortcuts and helpful console commands
$0..$4 – references to recently inspected DOM nodes.
$$('selector') – document.querySelectorAll shorthand.
debug(functionName) – tells DevTools to break when functionName is called (in some browsers).
4. Breakpoints & editing
Breakpoint types
Line breakpoints – pause execution at a specific line.
Conditional breakpoints – pause only when a JS expression evaluates true.
DOM breakpoints – pause when a node is modified (subtree changes, attribute change, node removal).
XHR/fetch breakpoints – pause when network requests occur (URL substring match).
Event listener breakpoints – pause on DOM events (click, keydown, etc.).
Exception breakpoints – pause on exceptions (caught/uncaught).
Using conditional breakpoints (example)
Right-click a line -> "Add conditional breakpoint..." -> enter user.id === 42 — this avoids stepping repeatedly.
Logpoints
A logpoint records a message when execution passes a line without stopping. Use Add logpoint... and enter user=${user.id}.
Live-editing
DevTools allows editing JavaScript in the Sources panel — useful for quick experiments. For persistent changes, update source files and rebuild.
5. Debugging Asynchronous Code
Key concepts:
Call stack: Synchronous function frames. When an async callback runs later, its stack starts fresh; traditional stack traces won't capture earlier context unless helps (async stack traces).
Microtask vs macrotask:
Microtasks: Promise callbacks, queueMicrotask, MutationObserver. They run after the current task but before rendering.
Macrotasks: setTimeout, setInterval, I/O callbacks, UI events.
Why this matters: bugs from timing and ordering (race conditions) often require reproducing the task order.
Debugging pattern for async bugs
Reproduce deterministically — use mocking or controlled time (e.g., sinon.useFakeTimers).
Add strategic breakpoints or logpoints in callbacks and promise chains.
Use async/await to make asynchronous flow more explicit while debugging.
Example — missing await bug
async function getUserAndData() {
const user = fetch('/api/user').then(r => r.json()); // OOPS: missing await
const posts = await fetch('/api/posts').then(r => r.json());
console.log(user.name, posts.length);
}
Symptom: user is a Promise, accessing user.name is undefined or error.
Fix: add await: const user = await fetch(...).then(r => r.json()); or use const { name } = await fetch(...).then(r => r.json());.
Asynchronous stack traces
Modern browsers and Node can show async stacks that cross Promises — enable the setting "Enable async stack traces" in your debugger if available.
6.Performance Profiling
CPU profiling
Use the Performance panel to record activity while reproducing the issue.
Look for long tasks ( >50 ms) and heavy scripting events.
Use the Flame Chart to identify hot functions and expensive call chains.
Memory profiling
Open Memory panel.
Take a Heap snapshot; interact with UI; take another snapshot.
Compare snapshots to find leaking growth.
Use Allocation instrumentation to see what code created the objects.
Look for detached DOM nodes — nodes that remain in memory but are not in document — common source of leaks.
Example leak pattern (common): adding event listeners on elements that are removed, without removing the listeners or without letting closures go out of scope.
example (concept)
function renderList(items) {
const container = document.getElementById('list');
items.forEach(item => {
const el = document.createElement('div');
el.textContent = item.text;
el.addEventListener('click', () => {
console.log('clicked', item);
});
container.appendChild(el);
});
}
// Suppose renderList is called repeatedly without clearing the container — event listeners keep references to `item`, preventing GC.
Fixes: remove event listeners when removing elements, use event delegation, or clear container references.
7. Node.js & VS Code Debugging
Quick start with Node
Start Node with node --inspect index.js or to pause on first line: node --inspect-brk index.js.
Open chrome://inspect in Chrome and attach to running process.
Alternatively, use ndb (an improved Node debugger) or VS Code built-in debugger.
VS Code launch.json example
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/index.js",
"runtimeArgs": ["--inspect-brk=9229"]
}
]
}
Attach remotely: expose the inspector port (9229) and set address/port in config.
8. Each includes: goals, steps, expected outcome, hints, and a short solution explanation. Time estimates assume who can run a local development server.
Console & Breakpoints
Goal: use line breakpoints, conditional breakpoints, logpoints, and the console to find a logical bug.
Repro repo: 1/index.html (simple page)
Buggy code (index.html):
<!doctype html>
<html>
<body>
<button id="btn">Click</button>
<script>
const btn = document.getElementById('btn');
let count = 0;
btn.addEventListener('click', () => {
count++;
// BUG: assignment used instead of comparison
if (count = 5) {
alert('Five clicks!');
}
});
</script>
</body>
</html>
Steps:
Open DevTools -> Sources -> set breakpoint on the if (count = 5) line.
Click the button a few times; observe the breakpoint and inspect count in the Scope pane.
Use the Console to evaluate count and see its value before/after.
Fix the bug to if (count === 5) and verify.
Expected outcome: identifies comparison, fixes it,
Hint: Put a conditional breakpoint count === 5 instead of a breakpoint that always stops.
Solution explanation: count = 5 sets count to 5 and returns 5 (truthy) so the if runs every click — the observed behavior.
Async Condition Goal: Reprod
Done — I created a canvas document titled Debugging Techniques and DevTools in JavaScript.
No comments:
Post a Comment