第四章:Sources 面板与断点调试

调试这件事,说白了就是跟代码「对话」。你问它一句「你现在跑到哪了?」,它回你一个变量值。但很多时候,代码跑得太快,你根本来不及问。这时候就需要断点——让代码在关键位置停下来,等你慢慢审问。

我个人习惯,调试时 80% 的时间都泡在 Sources 面板里。Chrome 的这套工具,用熟了真的能省下大把时间。今天我就带你把它摸透。

4.1 设置断点:让代码停下来

断点就是「暂停指令」。你告诉浏览器:「跑到这一行,先别动,等我看看情况。」

行断点

最基础的操作。打开 Sources 面板,找到你要调试的 JS 文件,在行号上点一下——蓝色箭头就出现了。

// 比如这段代码
function calculateTotal(price, quantity) {
  let subtotal = price * quantity;    // 在这行点一下
  let tax = subtotal * 0.08;
  return subtotal + tax;
}

点完之后刷新页面,代码执行到这一行就会停住。嗯,就这么简单。

小技巧: 右键点击行号可以设置「编辑断点」,输入一个表达式。只有表达式为 true 时才会暂停。这在循环里特别有用。

条件断点

我在项目中遇到过一个问题:一个循环跑了 10000 次,但只有第 5000 次才出错。你总不能手动点 5000 次「继续」吧?

条件断点就是干这个的。右键行号,选择「添加条件断点」,输入:

i === 5000

只有 i 等于 5000 时才会停。省心省力。

DOM 断点

这个我特别喜欢。有时候页面上的某个元素突然消失了,或者样式变了,你根本不知道是哪段代码干的。

在 Elements 面板里,右键目标元素 → Break on → 选择:

  • Subtree modifications:子节点变化时暂停
  • Attribute modifications:属性变化时暂停
  • Node removal:元素被移除时暂停

选完之后,一旦有 JS 动了这个元素,调试器就会自动跳到对应的代码行。我曾经用这个功能抓到一个第三方脚本偷偷改 DOM 的问题——那感觉,就像抓到了小偷现行。

XHR 断点

网络请求出问题?别在 Network 面板里瞎翻。直接告诉调试器:「只要发请求,就给我停。」

在 Sources 面板右侧,找到 XHR/Fetch Breakpoints,点加号。你可以输入 URL 关键字来过滤,比如:

/api/user

这样只有包含 /api/user 的请求才会触发断点。调试接口调用顺序时,这招特别好使。

4.2 单步执行:一步一步看代码怎么跑

断点停住了,然后呢?你得学会「走」着看代码。

操作 快捷键 作用
Step Over F10 执行当前行,跳到下一行(不进入函数内部)
Step Into F11 进入当前行调用的函数内部
Step Out Shift+F11 跳出当前函数,回到调用处

Step Over 是最常用的。你一行一行往下走,看看变量怎么变的。如果遇到一个函数调用,你不想进去看细节,就按 F10 跳过。

Step Into 适合你怀疑某个函数内部有问题。按 F11 钻进去,一行一行排查。我记得有一次,一个排序函数总出 bug,我 Step Into 进去才发现——里面有个隐藏的递归调用,自己把自己绕晕了。

Step Out 是救命用的。你不小心钻进了一个工具函数,发现里面逻辑太复杂,不想看了。按 Shift+F11 直接跳出来,回到原来的位置。

注意: 别在第三方库的代码里 Step Into 太久。那些压缩过的代码,进去就是一堆 a、b、c 变量名,看了头疼。直接 Step Over 跳过就好。

4.3 Call Stack 与 Scope:看清「谁叫我」和「我有什么」

代码停住之后,右侧面板有两个东西你必须看懂:Call Stack 和 Scope。

Call Stack(调用栈)

说白了就是「谁叫了谁」。比如:

function A() { B(); }
function B() { C(); }
function C() { debugger; }  // 停在这里

Call Stack 会显示:

  • C(当前停的位置)
  • B(谁调了 C)
  • A(谁调了 B)
  • (anonymous)(全局作用域调了 A)

你点一下 Call Stack 里的任意一行,Scope 面板就会切换到那个函数的上下文。这招在排查「事件冒泡」或者「异步回调」时特别有用——你能看到整个调用链路,而不是只盯着当前这一层。

Scope(作用域)

这里显示当前能访问到的所有变量。分三种:

  • Local:当前函数的局部变量
  • Closure:闭包捕获的外部变量
  • Global:全局变量(比如 window 上的东西)

我建议你养成一个习惯:每次断点停住,先扫一眼 Scope。看看变量值是不是你预期的。如果发现某个变量是 undefined 或者 NaN,那问题基本就锁定了。

避坑指南: 我曾经调试一个异步函数,发现变量值总是不对。看了半天代码逻辑没问题,最后发现是 Scope 里显示的是「旧值」——因为闭包捕获的是引用,而不是副本。嗯,从那以后我每次看到闭包变量,都会多留个心眼。

4.4 实战小技巧

最后分享几个我常用的调试套路:

  1. debugger 语句:在代码里直接写一行 debugger;,效果等同于手动加断点。适合在条件分支里动态触发调试。
  2. 黑盒脚本:右键 Call Stack 里的某个文件,选择「Blackbox Script」。这样调试时就不会再进入这个文件,适合屏蔽第三方库。
  3. Watch 面板:在右侧 Watch 区域点加号,输入表达式(比如 document.title),它会实时计算值。比在 Scope 里翻来翻去快多了。

调试不是玄学,是手艺活。多练几次,你就能从「代码为什么错了」进化到「我知道它会在哪错」。这个感觉,很爽。