Skip to content

前端调试技巧 ​

同样一个 bug,有人改了十次代码还没找到原因,有人三分钟就定位了。差距往往不在于对工具多熟悉,而在于方法:前者看到问题就开始改代码“试试看”,后者先观察、再假设、最后验证。

这篇先讲一套通用的排查方法,再介绍 DevTools 里最有用的几种工具,最后用一个真实场景完整走一遍。

读完这篇你会

  • 用“复现 → 缩小范围 → 观察 → 假设 → 验证”的流程排查问题
  • 用好条件断点、DOM 断点、请求断点、异常断点
  • 用网络面板判断是前端问题还是接口问题,并在本地模拟接口返回

1. 原理:调试就是不断缩小范围 ​

调试和查电路故障是一回事。一长串灯泡不亮了,最快的办法不是挨个换灯泡,而是从中间断开,看问题在前一半还是后一半,然后对有问题的那一半继续对半分。每一步都让“可能的原因”减少一半。

text
1. 复现:能稳定复现,才能验证修复是否有效
2. 缩小范围:二分法——注释掉一半代码、回退到上一个版本、换一份数据
3. 观察:看实际的值,而不是你以为的值
4. 假设:提出一个具体的、可以被验证的猜测
5. 验证:只改一处,看现象是否随之改变

最常见的反模式是跳过 1~3,直接改代码看效果。每次改动都引入新的变化,问题会越来越乱。

最容易卡住的时刻,往往是你心里想着“这里肯定没问题”。放下这个判断,去看它的实际值。

2. 先让 bug 稳定复现 ​

“偶发”的 bug 通常只是触发条件没找到。这些手段能让很多偶发问题变成必现:

手段能暴露什么
Network 面板把网速调成 Slow 4G竞态、重复提交、加载状态没处理
Performance 面板把 CPU 降速 4~6 倍时序问题、低端手机上的卡顿
无痕窗口 / 清空本地存储依赖缓存、旧数据导致的问题
换一个账号、换一份数据权限、空数据、超长数据导致的问题
快速连续操作并发、状态没重置

能复现后,再尽量把它缩成最小复现:删掉无关的组件和数据,直到只剩下触发问题必需的部分。很多时候,缩到最小的过程中原因就自己浮现了。

3. 断点:比 console.log 强在哪 ​

console.log 只能看你事先想到要打印的值;断点能让程序停在某一刻,看到这一刻所有变量的值、调用栈,还能一步一步往下执行。

断点类型怎么设置适合什么场景
普通断点Sources 面板点击行号看某一行执行时的状态
条件断点右键行号 → Add conditional breakpoint循环第 500 次才出错:条件写 i === 500
日志断点右键行号 → Add logpoint不改代码、不暂停,只打印(相当于临时 console.log)
DOM 断点Elements 面板右键元素 → Break on找出是哪段代码修改、删除了这个元素
请求断点Sources → XHR/fetch Breakpoints某个接口被调用时暂停,看是谁发的请求
事件断点Sources → Event Listener Breakpoints点击、按键时暂停,找到真正处理事件的代码
异常断点Sources → 勾选 Pause on caught exceptions错误被某个 catch 吞掉时,也能停在抛出的位置

停在断点后最常用的几个区域:

  • Scope:当前作用域里所有变量的值,包括闭包捕获的变量;
  • Call Stack:是谁一层层调用到这里的,点击任意一层可以跳过去看当时的状态;
  • Watch:添加表达式,每次暂停时自动计算。

单步执行时总是钻进框架和第三方库的源码?在 Call Stack 里右键对应的文件,选择 “Add script to ignore list”,以后单步就会跳过它们。

4. 网络面板:先判断是谁的问题 ​

很多“前端 bug”其实是接口问题,反过来也一样。打开 Network 面板,找到相关的请求,依次看:

看什么能判断什么
请求是否发出、发了几次没发 → 前端逻辑问题;发了两次 → 重复提交
请求的 URL、参数、请求体参数不对 → 前端问题
状态码4xx 多为请求本身有问题,5xx 是服务端出错
响应体数据为空、字段缺失、格式不对 → 接口问题
Timing等待服务端响应(Waiting for server response)很长 → 后端慢

几个实用操作:

  • Preserve log:页面跳转后保留请求记录,排查登录跳转、表单提交后跳页的问题时必开;
  • Disable cache:排除缓存干扰;
  • 右键请求 → Copy → Copy as cURL / Copy as fetch:把完全相同的请求复制出来,发给后端同事复现,或在控制台里重放;
  • 本地覆盖响应:右键请求 → Override content,可以修改接口返回的内容并保存在本地。刷新页面后,浏览器会用你修改后的响应。用它来模拟“接口返回空数组”“字段为 null”“返回 500”,不需要后端配合就能测试前端的各种分支。

5. 一个完整的排查案例 ​

问题:用户反馈“编辑资料后点保存,偶尔会提示保存成功,但刷新后资料没变”。

  1. 复现:在本地操作十次都正常。把网速调成 Slow 4G 后再试,第三次就出现了。说明和网络速度有关。
  2. 观察:Network 面板里,一次“保存”发出了两个 PUT /profile 请求,第二个请求的请求体里是修改前的旧数据。
  3. 缩小范围:给这个接口加请求断点,两次暂停时分别看 Call Stack。第一次来自点击“保存”按钮;第二次来自一个“离开页面前自动保存”的逻辑,它在保存成功、页面跳转时触发了。
  4. 假设:自动保存读取的是组件初始化时缓存的表单数据(旧值),在慢网络下它比正常保存更晚到达服务器,于是用旧数据覆盖了新数据。
  5. 验证:在自动保存的函数里打断点,Scope 中的表单数据确实是旧值。修复方式是:自动保存读取最新的表单状态,并且在一次保存进行中时不再发起新的保存。修复后用 Slow 4G 重复操作二十次,问题不再出现。

整个过程没有“猜着改”,每一步都基于观察到的事实。

6. 线上问题怎么查 ​

线上问题无法打断点,要靠事先准备好的信息:

  • 错误监控 + Source Map:线上代码是压缩过的,上传 Source Map 到监控平台,堆栈才能还原到源码行(不要把 Source Map 公开部署);
  • 有上下文的日志:
js
// ❌ 看不出发生了什么
console.log('error', err)

// ✅ 带上操作、关键参数和错误信息(不要记录密码、token 等敏感信息)
console.error('[profile] save failed', { userId, fields: Object.keys(changes), message: err.message })
  • 找出是哪次提交引入的:问题在某个版本之后才出现,但不知道是哪次改动?用 git bisect 做二分查找:
bash
git bisect start
git bisect bad              # 当前版本有问题
git bisect good v1.4.0      # 这个版本是好的
# Git 会切到中间的某个提交,你测试后告诉它结果:
git bisect good             # 或 git bisect bad
# 重复几次后,Git 会指出第一个出问题的提交
git bisect reset            # 结束,回到原来的分支

一百个提交,最多测七次就能找到元凶。

移动端和 App 内置浏览器的调试方法,见 移动端适配 的排查部分。

7. 常见现象速查 ​

现象先查什么
数据没显示Network 里接口的响应是不是空的、字段名对不对
样式没生效Elements 面板里规则是否被划掉(被覆盖),选择器是否匹配
点击没反应控制台有没有报错;元素是否被透明层遮挡(Elements 面板悬停查看)
只有部分用户出问题按浏览器版本、账号权限、数据特征分组对比
偶发问题慢网络、CPU 降速、快速重复操作
页面越用越卡Memory 面板拍堆快照对比,查找泄漏
错误被吞掉了勾选 Pause on caught exceptions

检查清单 ​

  • [ ] 先稳定复现,必要时缩成最小复现
  • [ ] 观察实际值,不靠“应该是这样”的判断
  • [ ] 每次只改一处,验证现象是否改变
  • [ ] 会用条件断点、DOM 断点、请求断点和异常断点
  • [ ] 先用 Network 面板区分前端问题和接口问题
  • [ ] 线上有错误监控、Source Map 和有上下文的日志
  • [ ] 修复后用当初能复现问题的条件再验证一遍

延伸阅读 ​

为复用而记录,为理解而整理。