浏览器 DevTools 十分钟入门

浏览器 DevTools 不是一堆隐藏技巧的合集。它是从「页面坏了」走到 DOM、CSS、请求、JavaScript 和渲染证据的最短路径。我的原则是:别一上手就往代码里到处撒 console.log。先弄清是哪一层在说谎。

🎙️ 发布并录制于: ·

01从出问题的那一层开始

用 F12、Control Shift I 或 Command Option I 打开 DevTools。然后给症状分类。形状或颜色不对,属于 Elements。数据没出来,属于 Network。交互点了没反应,属于 Console 和 Sources。页面慢,属于 Network 的时序面板或 Performance。刷新之后还在的状态,属于 Storage。这个分类只花十秒,能省掉半小时乱改代码。

# Symptom → first panel
button is hidden              → Elements / Styles
API data is absent            → Network / Fetch-XHR
click does nothing            → Console, then Sources
works only after hard refresh → Network cache + Application storage
page freezes while scrolling  → Performance
keyboard cannot reach control → Elements / Accessibility
一条硬规矩

别先加 console 日志。日志会改变时序,抓不到日志之前就已经发生的失败,而且只能告诉你你猜着要打印的那些东西。先把失败的页面保留住,看现有的 Console 输出和请求记录,再在执行开始偏离的地方下断点。只有断点抓不到运行环境时,才加一条有针对性的日志。

02Elements 和 Styles:看浏览器眼中的事实

Elements 树是活的 DOM,不一定等于你写的那份 HTML 文件。用元素选择器点中出问题的节点,看命中的规则、被划掉的声明、继承来的值,还有 Computed 面板。把一条声明关掉,而不是删掉。先在 DevTools 里临时加一个属性,确认这样能修好,再回源码里改。刷新一下,实验就没了。

/* The declaration is crossed out because specificity wins */
.card button { color: white; }
#checkout .card button { color: black; }

/* Inspect Computed → color, then follow the winning rule. */
/* Fix the selector design; do not reach for !important by reflex. */
.checkout-button { color: white; }
盒子在,但看不见

第一步:在 Computed 里检查 display、visibility、opacity、尺寸、overflow 和 transform。第二步:往上看祖先元素有没有裁剪和层叠上下文。第三步:把可疑的那条规则开关一次。第四步:把改好的声明抄回样式表。如果 DOM 节点本身就不存在,别再调 CSS 了,去看渲染条件或者请求数据。

03Console:读第一条真正有用的报错

只有在页面跳转本身是 bug 一部分时,才开 Preserve log。清空 Console,复现一次,从第一条红色记录往下读。后面的报错常常只是连带反应。点文件名和行号可以跳到 Sources。展开对象要留个心:打印出来的对象可能显示的是后来被改过的状态,所以在意历史值的时候,记录一份拷贝快照。

Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
    at app.js:18:9

// Step 1: click app.js:18 and inspect the selector result.
const button = document.querySelector("#save"); // null
// Step 2: verify the live DOM and script timing.
// Step 3: fix the ID, or load code after the DOM exists.
document.addEventListener("DOMContentLoaded", init);

如果这个按钮是必需的,别用可选链「修好」它。那只是把一个吵闹的接线错误,换成一个安静的死按钮。先确认到底是选择器写错了、组件没渲染,还是脚本跑得太早,然后修真正的那份约定。

04Network:看真实发出的请求,不是你以为的请求

先打开 Network 再复现,因为它只在打开时才记录。筛选到 Fetch 或 XHR,选中请求,检查状态码、最终 URL、方法、请求头、请求体、响应头、响应体、发起者和时序。需要把浏览器行为和服务端行为分开时,用 Copy as cURL,但分享之前记得删掉 cookie 和 authorization。

GET https://example.com/api/users 404 (Not Found)
Access to fetch at 'https://api.example.net/users' from origin
'https://example.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present.
两种失败,两种修法

遇到 404,看最终的 Request URL,和线上服务的路由对一遍,检查 base path 和大小写,然后直接请求这个确切的 URL。遇到 CORS blocked,找到失败的那个请求或 OPTIONS 预检,看它的响应头,再在 API 服务端放行确切的 origin、方法和请求头。别去装浏览器插件,也别想在前端 JavaScript 里加这个响应头,这个许可归服务端管。

05请求时序和缓存:搞清时间花在哪

一个慢请求不是一个数字。Queueing 长,可能是连接数限制,也可能是主线程被占住。DNS、连接和 TLS 指向建连成本。Waiting,也就是常说的首字节时间,指向服务端或网络延迟。Content download 指向响应体大小,或者流本身很慢。先看 Timing 面板,再决定这个延迟该归谁。

# Example timing interpretation
Queueing       2 ms
DNS            0 ms       # reused connection or cached lookup
Initial conn   0 ms
Waiting      842 ms       # investigate server work / upstream latency
Download      18 ms       # payload transfer is not the problem

Cache-Control: public, max-age=31536000, immutable
Cache-Control: no-cache  # may store; must revalidate
Cache-Control: no-store  # must not store

Disable cache 只在 DevTools 打开时生效,也只用来做一次可控的对比。先按正常缓存复现一次,再对比禁用缓存的结果,然后看 Size 列显示的是 memory cache、disk cache,还是真实传输字节。如果用户看到的是旧的带哈希文件名的资源,先查 HTML 的缓存和 service worker,别急着怪静态资源缓存。

06Storage:检查状态,不要一键清空

Application 面板里有 cookie、local storage、session storage、IndexedDB、cache storage 和 service worker。清掉之前,先把那个确切的键看清楚。「Clear site data」会毁掉证据,还可能让 bug 消失,而你并没弄明白原因。看 cookie 要确认 domain、path、过期时间、SameSite、Secure、HttpOnly,以及这次请求到底有没有带上它。

# Cookie exists in Storage but is absent from the request?
Set-Cookie: session=abc; Path=/; Secure; HttpOnly; SameSite=Lax

# Check in order:
1. Request URL matches Domain and Path
2. HTTPS is used when Secure is set
3. SameSite permits this request context
4. Fetch uses credentials when cross-origin
5. Third-party-cookie policy did not block it

如果一个过期的 service worker 还在提供旧版应用,打开 Application,看激活中的 worker 和缓存名称,为这一次测试打开 update on reload,记录好状态之后再取消注册。线上的真正修法是带版本号的缓存加一套明确的激活策略,而不是教每个用户去清浏览器。

07Sources:在错误值出现的地方暂停

断点会把程序冻住,局部变量、作用域、调用栈和触发它的事件都还在。在走错的分支之前那一行下断点。只有一条记录出错时用条件断点,按 URL 片段拦请求用 XHR 断点,点击行为莫名其妙时用事件监听断点,某个库把有用的报错吞掉了就打开 pause on caught exceptions。

// Conditional breakpoint expression:
order.id === "ord_1842" && order.total < 0

// Then inspect, without editing production code:
order
order.items
subtotal
new Error().stack

DevTools failed to load source map:
Could not load content for /assets/app.js.map: HTTP error: status code 404
source map 404 的修法

第一步:看构建出来的 JavaScript 文件末尾的 sourceMappingURL。第二步:在 Network 里打开解析后的那个 map 地址,确认确实是 404。第三步:把 map 上传到这个确切路径,或者改掉构建产物里的引用。第四步:如果 map 必须保密,就删掉这条公开引用,只把 map 上传到错误监控平台。应用照样能跑,但调压缩后的代码会白白受苦。

08Performance:只录一次交互

录最小的那段慢交互:开始录制,做一个动作,停止。先找主线程上的长任务,再把它展开成脚本执行、样式重算、布局、绘制和调用树。黄色是 JavaScript 的工作,紫色指向样式和布局,绿色指向绘制。颜色是线索,不是判决;选中那个事件,看它是被谁触发的。

# A common layout-thrashing pattern
for (const row of rows) {
  row.style.width = container.offsetWidth + "px"; // read/write repeated
}

# Batch the read, then the writes
const width = container.offsetWidth;
for (const row of rows) row.style.width = width + "px";

用 CPU 降速可以暴露那些脆弱的交互,但要在结论里标明这是模拟结果。看加载性能就录一次刷新,把大资源或长任务和用户能感知到的等待对应起来。别去优化火焰图里最好看的那一块,要修真正卡住这次交互的工作。

09设备模拟和无障碍面板是筛子,不是证明

设备模拟很适合看视口宽度、响应式断点、触摸事件模拟、横竖屏和网络限速。它不是真的 iPhone,不是 Android 的 GPU,不是手机浏览器的界面,也不是屏幕键盘。用它找出可能出问题的地方,然后把重要路径放到真机上测。

# Quick responsive checks
320 CSS px wide · 200% zoom · landscape · slow network

# Accessibility checks in Elements
computed role · accessible name · keyboard focus · contrast

# Bad: clickable div with no keyboard semantics
<div onclick="save()">Save</div>
# Good: native behavior and accessible name
<button type="button">Save</button>

在 Accessibility 面板里看 role 和 name,然后用 Tab、Shift Tab、Enter、空格和 Esc 走一遍页面。自动检查能抓出缺失的标签和对比度不足,但它判断不了焦点顺序是否合理,也判断不了屏幕阅读器念出来的内容有没有用。原生 HTML 元素永远好过一个被修补过的 div。

10排查流程与速查表

保留故障现场,复现一次,顺着证据从用户看到的症状走到出问题的那一层。一次只改一个变量,用同样的步骤再测一遍,把推翻你第一个猜想的事实记下来。这比一次列出二十个可能原因快得多。

# Triage order
1. Reproduce with exact URL, account, viewport, and action
2. Console: first red error, file, line, call stack
3. Network: URL, status, payload, response, initiator, timing
4. Elements: live DOM, winning CSS, box and accessibility tree
5. Storage: exact cookie/key/worker involved
6. Sources: breakpoint before divergence; inspect call stack
7. Performance: record one slow action
8. Fix source, reload cleanly, repeat original steps

# Error shorthand
404 → verify final URL and deployed route
CORS blocked → inspect preflight; fix API response headers
Uncaught TypeError → click line; inspect value and lifecycle
source map 404 → fix map URL/deployment or remove reference
stale page → compare cache, service worker, and storage

# Useful shortcuts
F12 / Ctrl+Shift+I / Cmd+Option+I  open DevTools
Ctrl+Shift+P / Cmd+Shift+P         command menu
Ctrl+Shift+C / Cmd+Shift+C         pick an element

犯罪现场大部分已经被浏览器录好了。我偏好的顺序是:先看 Network 再看日志,先下断点再考虑加日志,先录一小段性能再谈优化。DevTools 变强的时刻,是它替你省掉猜测,而不是你把每个面板都背下来。

Tell me what missed

A correction is more useful than a compliment. This goes straight to the person who writes SwiftGrasp.

Was this page useful?
0/1000

Please do not include passwords, private keys, or personal information.