上一章我们学习了如何设置断点,让 JavaScript 代码在指定的位置暂停下来。但“暂停”只是调试的起点——真正的关键在于暂停之后做什么。

想象一下这个场景:你开车时发现了一个故障,于是把车停下来。停下来的下一步是什么?是打开发动机盖,检查每一个零件,看看哪里出了问题。调试也是一样——断点让你的代码“停下来”,而调试器控制让你能够打开发动机盖,逐一检查每一个零件。

代码暂停之后,DevTools 为你提供了一套完整的控制按钮:继续执行、单步跳过、单步进入、单步跳出等。除了这些控制按钮,右侧的调试区还提供了调用栈(Call Stack)、作用域(Scope)和 监视(Watch)三个强大的辅助工具,让你在单步执行的过程中,时刻掌握代码的完整状态。

这一章,我们来学习调试器的所有控制方法和辅助工具。学完之后,你将能够像看慢动作电影一样,逐帧观察 JavaScript 代码的执行过程。

小提示:这一章的内容非常实操。建议你打开一个包含 JavaScript 的页面(可以用上一章的示例),设置一个断点,然后跟着文章一步步操作,感受每个控制按钮的实际效果。

一、代码暂停之后:调试器界面速览

当代码在断点处暂停时,Sources 面板会发生几个明显的变化:

编辑器区域:

当前暂停的那一行代码会被黄色高亮显示。这让你一目了然地知道代码停在了哪里。

调试工具栏:

页面顶部位置会出现一组快捷控制按钮(F8、F10)。同时,BreakPoints 面板顶部(Sources面板右侧)的调试控制按钮组会被激活。这些按钮就是你的“指挥台”——通过它们来控制代码的下一步走向。

右侧调试区:

Sources面板右侧的 Debugger 区域会展示当前时刻的完整状态——包括调用栈(Call Stack)、作用域中的变量(Scope)、以及你设置的监视表达式(Watch)等。

二、调试控制按钮详解

代码暂停后,BreakPoints 面板顶部调试控制按钮组会被激活。我们用一表来逐一认识它们:

Chrome DevTools 调试控制按钮组

控制操作

快捷键

说明

Resume/Pause script execution

F8 或 Ctrl + \

恢复执行,直到下一个断点/暂停脚本执行

Step over next function call

F10 或 Ctrl + '

走一步,停一下;遇函数直接跳过,不进函数

Step into next function call

F11 或 Ctrl + ;

走一步,停一下;遇到函数,直接进去

Step out of current function

Shift + F11 或 Ctrl + Shift + ;

走完函数,回到当初调用这个函数的那一行代码

Step

F9

走一步,停一下;逐行走,遇到函数,直接进去

Activate/Deactivate breakpoints

Ctrl + F8

启用/禁用断点

为什么 F9 和 F11 听起来很像?

在 Chrome 中,如果你当前高亮的这一行就是函数调用(比如 sum = add(1, 2);),按 F9​ 和按 F11​ 的效果完全一样——都会钻进 add 函数里。

细微区别在于:

  • F9(Step)​ 是“无差别逐语句”,它只关心“下一行代码”,是普通代码就执行普通代码,是函数就进函数。

  • F11(Step into)​ 是“专门用来进函数的”,当你确定下一行是函数且你想进去时,用 F11 更符合直觉。

但在日常简单调试中,你可以把它们当成一回事:F9 和 F11 都会进函数,F10 绝不进函数。

三、辅助调试工具

控制按钮让你“指挥”代码的执行节奏,而右侧 Debugger 区域中的辅助工具则让你在每一步都能“看清”代码的内部状态。

1. 调用栈(Call Stack)

它是什么:

调用栈显示了当前执行位置向上追溯的所有函数调用链。

怎么理解:

想象你在一栋楼里,每进入一个函数就像上一层楼。调用栈从下往上列出了你走过的所有楼层——最底部是最早调用的函数,最顶部是当前所在的函数。

实际用途:

当代码暂停时,调用栈能帮你回答一个关键问题: “我是怎么来到这里的?” 如果你在一个复杂的代码库中调试,这个信息至关重要——它让你理解代码的执行路径,而不是孤立地看当前这一行。

操作提示:

点击调用栈中的任意一个函数名,编辑器会自动跳转到该函数的定义位置,方便你查看上下文。

2. 作用域(Scope)

它是什么:

作用域面板显示了当前暂停位置所有可访问的变量及其值。

实际用途:

当代码暂停时,你不需要手动去猜某个变量的值——Scope 面板已经帮你把所有变量都列好了。你可以一目了然地看到 addend1 是 "5"(字符串)而不是 5(数字),从而快速发现类型问题。

操作提示:

在 Scope 面板中,双击任意变量的值可以直接编辑它。这是一个非常实用的小技巧——如果你想测试“如果这个变量的值是 X,代码会怎么走”,可以直接在 Scope 面板中修改,而不需要重新运行整个流程。

3. 监视(Watch)

它是什么:

Watch 面板允许你添加自定义的表达式,DevTools 会在每一步执行后自动显示这些表达式的当前值。

怎么用:

点击 Watch 面板中的 + 按钮,输入你想要追踪的表达式——可以是一个变量名(如 sum),也可以是一个更复杂的表达式(如 a + b 或 user.name)。

和 Scope 的区别:

  • Scope:显示的是当前作用域中所有变量,由 DevTools 自动列出。

  • Watch:显示的是你主动指定的表达式,只追踪你关心的那几项。

实际用途:

当你在单步执行过程中特别关注某个变量的变化时,把它加到 Watch 中。每次按 F10 或 F11 时,Watch 面板都会自动更新,让你持续追踪它的变化——不需要每次都在 Scope 面板里翻找。

小建议:Watch 和 Scope 是互补的。Scope 帮你“全面了解”,Watch 帮你“重点追踪”。调试时可以先看 Scope 了解全局,再把关键变量加到 Watch 中持续关注。

四、一个完整的调试流程

让我们把前面学到的所有内容串起来,走一遍完整的调试流程。

场景

你有一个计算两个数之和的页面。用户在两个输入框中分别输入 5 和 1,点击按钮后,页面显示 5 + 1 = 51。结果应该是 6,但页面算出了 51。

示例代码
<p id="label"></p>

<label id="label1">
    <input type="number" id="addend1Input">
</label>

<label id="label2">
    <input type="number" id="addend2Input">
</label>

<button onclick="updateLabel()">Update</button>

function updateLabel() {
    var label = document.getElementById('label');
    var addend1 = getNumber1();
    var addend2 = getNumber2();
    var sum = addend1 + addend2; // 此行设置断点
    label.innerHTML = addend1 + ' + ' + addend2 + ' = ' + sum;
}

function getNumber1() {
    var addend1Input = document.getElementById('addend1Input');
    return addend1Input.value;
}

function getNumber2() {
    var addend2Input = document.getElementById('addend2Input');
    return addend2Input.value;
}
调试步骤

第一步:设置断点

打开 Sources 面板,在 Page 标签页中找到相关的 JavaScript 文件,在 updateLabel 函数内部设置一个行断点。

第二步:触发断点

在页面中输入 5 和 1,点击按钮。代码在断点处暂停,当前行被黄色高亮。

第三步:检查 Scope

查看右侧的 Scope 面板。你会看到:

  • addend1 的值是 "5"(字符串,带引号)。

  • addend2 的值是 "1"(字符串,带引号)。

问题找到了!getNumber1() 和 getNumber2() 返回的是字符串,而不是数字。"5" + "1" 是字符串拼接,结果是 "51"。

第四步:单步执行验证

按 F10,执行 var sum = addend1 + addend2; 这一行。然后查看 Scope 面板——sum 的值是 "51"(字符串),确认了问题所在。

第五步:继续执行或结束调试

按 F8 继续执行,或者移除断点,回到代码编辑器中去修复源文件——把 getNumber1() 和 getNumber2() 的返回值用 Number() 转换成数字再相加。

五、调试中的常见问题

Q1:按了 F10 但代码没有动?

检查以下几点:

  1. 确认代码确实处于暂停状态(当前行被黄色高亮)。

  2. 确认 DevTools 窗口处于焦点状态(点击一下 DevTools 的任意位置再按快捷键)。

  3. 如果你在 Mac 上,确认是按 F10 而不是其他功能键——有些 Mac 键盘需要同时按 Fn + F10。

Q2:单步执行时不小心进入了不想看的函数(比如第三方库)怎么办?

按 Shift + F11,DevTools 会快速执行完当前函数的剩余代码,回到调用处。

你还可以在设置中配置“黑盒脚本”(Blackboxing),将第三方库的脚本标记为黑盒,这样单步执行时会自动跳过这些文件——我们会在后续章节中详细讲解。

Q3:Scope 面板中看不到某个变量?

可能的原因:

  1. 该变量不在当前作用域中——检查变量是否定义在函数外部或另一个闭包中。

  2. 变量被优化掉了——在代码压缩或编译过程中,某些变量可能被重命名或移除。

  3. 检查 Scope 面板的不同层级——展开 Closure 和 Global 查看。

Q4:Watch 面板中的表达式显示为 undefined?

可能的原因:

  1. 该变量在当前作用域中不存在。

  2. 表达式有语法错误。

  3. 变量尚未被赋值——单步执行到赋值语句之后再看。

总结

在这一章中,我们学习了代码暂停后的调试控制方法和辅助工具:

调试控制按钮:
  • 继续执行(F8):恢复执行,直到下一个断点/暂停脚本执行。

  • 单步跳过(F10):走一步,停一下;遇函数直接跳过,不进函数——日常最常用。

  • 单步进入(F9):逐行走,遇到函数,直接进去。

  • 单步进入(F11):走一步,停一下;遇到函数,直接进去。

  • 单步跳出(Shift + F11):走完函数,回到当初调用这个函数的那一行代码。

  • 启用/禁用断点(Ctrl + F8):启用或者临时禁用断点。

辅助调试工具:
  • 调用栈(Call Stack):显示完整的函数调用链,回答“我怎么来到这里的”。

  • 作用域(Scope):显示当前所有变量及其值,双击可直接编辑。

  • 监视(Watch):追踪你指定的表达式,在每一步自动更新。

断点让代码“停下来”,而调试器控制和辅助工具让你在停下来之后能够“看清楚”一切。从设置断点到单步执行,从检查 Scope 到追踪 Watch——这套完整的调试工作流,就是你从“遇到问题靠猜”进化到“遇到问题有方法”的关键。

从今天开始,当你遇到 JavaScript 的问题时,试着用这套流程来排查:设置断点 → 触发暂停 → 检查 Scope → 单步执行 → 定位问题。多练习几次,它就会成为你的本能反应。

下一章预告:我们将学习 debugger 命令和日志点(Logpoint)等高级调试技巧——让你在不修改源代码的情况下,更灵活地控制调试过程。