「译」官方指南系列:JavaScript 调试指南(一)

✍🏼 写于 2022年09月13日    💡 更新于 2022年09月13日
❗️ 注意:离本文创建时间已经过去了 天,请注意时效性
🖥  说明:官方指南系列之一
📚  本文同步发布在 Craft:https://www.craft.do/s/kyKcQKCEQjAksv

希望通过这篇全面的 Chrome DevTools debugging 功能介绍,能让你的 debugging 过程更上一层楼。

首先你可能需要先在 这里 (英文,待译)学习基本的 debugging 技巧。

用断点暂停代码

通过设置一个断点,可以让代码在执行途中暂停。

查看 这里 (英文,待译)来学习如何设置一个断点。

在鼠标悬浮的时候查看类/函数属性

当代码执行暂停的时候,将鼠标悬浮在一个类或者函数名上的时候可以预览它的属性。

1

逐步运行代码

你的代码暂停后,你就可以一次只运行一行代码,以搞清楚代码的调用栈以及相关的属性值。

跳过(Step over)当前代码行

当你的断点暂停在一个与你当前调查的问题无关的代码行,且该代码行包含一个函数的时候,你可以点击 Step Over 来不进入该函数以继续执行代码。

step_over

图一👆🏻,上图中蓝色方框中 Step Over 的含义解释:

举个例子,假设你正在 debugging 下面的代码:

1
2
3
4
5
6
7
8
9
function updateHeader() {
  var day = new Date().getDay();
  var name = getName(); // A
  updateName(name); // D
}
function getName() {
  var name = app.first + ' ' + app.last; // B
  return name; // C
}

此时你的断点停在了 A 的位置。通过点击 Step over ,Devtools 将会执行你「Step over」的位置中的所有代码,在上述示例中「Step over」的是 A 位置的 getName 函数中的 BC 的位置,于是 Devtools 会将代码停在 D 的位置。

进入(Step into)当前代码行

如果断点停在一个跟你要调查的问题有关系的地方,而且这个地方包含一个函数调用的时候,点击 Step into ,可以更进一步的检查这个函数。

step_into

图二👆🏻,上图中蓝色方框中 Step into 的含义解释:

举个例子,假设你正在 debugging 下面的代码:

1
2
3
4
5
6
7
8
9
function updateHeader() {
  var day = new Date().getDay();
  var name = getName(); // A
  updateName(name);
}
function getName() {
  var name = app.first + ' ' + app.last; // B
  return name;
}

你的断点正停在 A 的位置,此时点击 Step into ,Devtools 将会执行这行代码,然后停在 B 的位置。

跳出(Step out)当前代码行

当断点停在一个与你正在调查的问题不相关的函数内部的时候,你可以点击 Step out 来执行该函数中剩余的代码。

step_out

图三👆🏻,上图中蓝色方框中 Step out 的含义解释:

举个例子,假设你正在 debugging 下面的代码:

1
2
3
4
5
6
7
8
9
function updateHeader() {
  var day = new Date().getDay();
  var name = getName();
  updateName(name); // C
}
function getName() {
  var name = app.first + ' ' + app.last; // A
  return name; // B
}

你的断点正停在 A 的位置,此时点击 Step out ,DevTools 将会把 getName() 函数中的剩余代码执行完,也就是 B 位置的代码,然后断点停在 C 的位置上。

继续运行代码到指定行

当 debugging 一个很长函数的时候,函数中会有很多与你正在调查的问题不相关的代码。

你可以选择一步一步的执行这些无关的行,但是这个过程会很枯燥。你也可以选择在一个想断点的代码行处设置一个断点,然后按下 Resume Script Execution (恢复脚本执行),不过除此之外,还有一个更快的方式。

你可以在你想要断点的代码行上右键,然后选择 Continue to here (继续执行到此处)。Devtools 将会恢复断点执行代码直到此处,然后停在这一行。

continue_to_here

图四👆🏻,选择 Continue to here

恢复脚本执行

你可以通过点击 Resume Script Execution 来继续在你断点暂停的位置执行代码。Devtools 会将代码一直执行下去直到遇到下一个断点(如果有)。

resume_script_execution

图五👆🏻,Resume Script Execution ,蓝框处。

强制执行

可以通过长按 Resume Script Execution ,然后选择 Force Script Execution ,来忽略剩下的所有断点,直接执行全部代码。

force_script_execution

图六👆🏻,选择 Force Script Execution

改变当前线程的上下文

当 debugging 设计到 Web Worker 或者 Service Worker 的时候,可以通过点击出现在 Threads 栏中的上下文列表项,来转换上下文。蓝色箭头表示当前选择的上下文。

change_threads_context

图七👆🏻,Threads (线程)栏,蓝色框的位置。

举个例子,假设有这么个场景,你的断点同时存在于你的主脚本和你的 service worker 脚本中。你想要查看 service worker 中的本地变量和全局变量,但是 Source 栏当前正显示的是你的主脚本的上下文。此时你就可以通过点击在 Threads 栏中的 service worker 入口来切换上下文以查看你想了解的变量了。

查看和编辑局部、闭包以及全局作用域中的变量/属性

当暂停在某一行的时候,使用 Scope 栏来查看和编辑位于局部、闭包和全局变量。

  • 双击属性值以修改之。

  • 非可枚举的属性会以灰色突出显示。

查看和修改变量

图八👆🏻,Scope (作用域)栏,蓝色框的位置。

查看当前调用栈

当暂停在某一行的时候,使用 Call Stack 栏来查看从代码执行一直到你当前暂停点以来的函数调用栈。

如果你的代码中有异步代码,可以将 Async 复选框勾选上,来启用异步函数调用栈。

点击其中的一个条目,来跳转到该条目所表示的函数的调用处。蓝色箭头图标表示当前正在高亮的函数。

查看函数调用栈

图九👆🏻,Call Stack (调用栈)栏,蓝色框的位置。

注意:如果代码没有暂停在某一行,那 Call Stack 栏是空的。

重新执行堆栈中的函数

有时候想观察某一个函数的运行情况,但是又不想重新运行整个 debugging 流程,你可以在断点暂停在这个函数内部的时候重新只单独执行这个函数,换句话说,你可以在调用栈中重新放入该函数的调用上下文。

注意:你可以重头执行在 Call Stack (调用栈)中的任何函数,除了 WebAssembly、async、和 generator 函数。

为了重新执行一个函数:

  1. 使用断点暂停函数,Call Stack 栏会记录函数的调用顺序。

  2. Call Stack 栏,右键一个函数,然后在出现的菜单中选择 Restart frame(重头执行函数)。

重新执行断点所在的函数

为了理解 Restart frame 如何执行,我们假设有以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
function foo(value) {
    console.log(value);
    bar(value);
}
 
function bar(value) {
    value++;
    console.log(value);
    debugger;
}

foo(0);

foo 函数接受 0 作为参数,然后通过 log 打印到控制台,然后调用 bar() 函数。相应的,bar 函数会将这个值自增1。

尝试以下面的方式重头执行这两个函数:

  • 复制上面的代码到一个 snippet (未翻译)中,然后 运行 (未翻译)它。断点会停在 debugger 的所在的 代码行

    ⚠️ 注意:当代码执行暂停的时候,不要在控制台执行当前调用栈中的函数,因为这可能会引起意外的错误。

  • 你会注意到当前的 debugger 会在其所在的函数的函数声明的右侧显示当前值:value = 1

注意函数声明处右侧的值

  • 重头执行 bar() 函数。

重头执行bar

  • F9 ,然后代码可以经过值自增的那一行,然后再次断到 debugger 处。

    注意看,值变成了 2: value = 2

重头执行debugger

  • 除此之外,你还可以在 Scope 栏,双击 value 值来编辑它将其设置成想要的值。

双击编辑值

  • 尝试重新执行 bar() 函数多次,会发现值会一直增加。

多次重复执行后值会一直增加

💡 震惊!为什么 value 不会重置成 0

函数重头执行的时候不会重置参数。换句话说,重头执行不会恢复该函数被调用时候的初始状态。因此,它只是简单的在调用栈中移动当前调用的指针到函数的开始位置。

因此,当前的参数值 value 会在内存中一直随着相同函数的重复执行而一直存在。

  • 现在,在 Call Stack 中重头执行 foo() 函数。

重头执行foo函数

注意看,value 重新变成了0

value重新变为0

💡 再次震惊!为什么 value 这次被重置为 0 了?

很简单(此处意译了)在 JavaScript 中,参数是按值传递的,因为 value 是原始值,因此在函数内修改它的值不会影响到函数外它的值。

复制调用栈执行路径

Call Stack 栏的任意位置右键,然后选择 Copy Stack Trace(复制栈追踪),你会将当前调用栈复制到粘贴板。

复制调用栈

图十👆🏻,选择 Copy Stack Trace

复制的内容大致会是这个样子:

getNumber1 (get-started.js:35)
inputsAreEmpty (get-started.js:22)
onClick (get-started.js:15)

忽略某个脚本或者满足某种条件的脚本

当 debugging 的时候,忽略某个脚本以跳过它。你通常会在当该脚本的函数比较复杂难懂而且跟你当前 debugging 的内容无关的时候选择忽略之。

例如,假设你正在 debugging 如下代码:

1
2
3
4
5
function animate() {
  prepare();
  lib.doFancyStuff(); // A
  render();
}

A 是一个你信任的第三方库。如果你十分确定你所调查的问题跟这个第三方库没有关系,那忽略它就是一个明智决定。

从 Source 编辑器栏中忽略某个脚本

  1. 打开文件

  2. 右键任何位置

  3. 选择 Add script to ignore list(将脚本添加到忽略列表)。

忽略脚本

图十一👆🏻,从编辑器栏中忽略一个脚本。

从 Call Stack (调用栈)栏忽略某个脚本

如果想从调用栈中忽略某个脚本,你需要:

  1. 在调用栈中的某个函数中右键。

  2. 选择 Add script to ignore list

从调用栈中忽略脚本

图十二👆🏻,从 Call Stack(调用栈)中忽略某个脚本。

从 Settings(设置)中忽略某个脚本

译者注:此处的设置为 Devtools 的设置,不是浏览器的设置。

如果想从设置中忽略某一个脚本或者满足某种条件的脚本,你需要:

  1. 打开设置:

  2. 点击 Ignore List Tab。

  3. 点击 Add pattern

  4. 输入脚本名或者正则来匹配需要忽略的脚本名。

  5. 点击 Add

devoools设置界面

devtools忽略脚本

图十三👆🏻,从 Setting 中忽略脚本。

从任何页面运行 debug 代码 Snippets(片段)

如果你发现你正在 Console 中反复运行一些 deubg 代码,那么可以考虑一下 Snippets。Snippets 是一种你可以存储在 Devtools 中并执行的脚本。

参见 https://developer.chrome.com/docs/devtools/javascript/snippets/ (未翻译)来了解更多。

译者注:Snippets 在执行的时候是携带当前上下文的,比如你在 debug 的时候暂停了,然后执行 Snippets 代码,此时的 Snippets 代码拥有当前上下文的变量访问权限。

Watch(监听)自定义的 JavaScript 表达式的值

使用 Watch 栏来监听自定义表达式的值。你可以监听任何有效的 JavaScript 表达式。

监听JavaScript表达式

图十四👆🏻,蓝色圈住的即是 Watch 栏。

  • 点击 Add Expression(添加表达式)来新建一个监视表达式。

  • 点击 Refresh(刷新)来刷新所有已经存在的表达式。当执行代码的时候,值会自动更新。

  • 鼠标悬浮在一个表达式上,然后点 Delete Expression(删除表达式),来删除它。

格式化压缩后的代码以可读

点击 Fromat {} 来让一个压缩后的代码变成人类可读的格式。

格式化压缩后的文件

编辑一个脚本

当修复一个 bug 的时候,你经常会需要测试你的 JavaScript 代码的修改效果。你不要在一个外部的编辑器中编辑 JavaScript 代码后,再回到当前页面刷新后查看效果。你可以直接在 Devtools 中编辑你的 JavaScript 代码。

如果想编辑脚本,你需要:

  1. 在 Source 栏中打开你想要编辑的文件(会出现在 Editor 栏中)。

  2. 在 Editor 栏中做出修改。

  3. Cmmand + S (Mac)或者 Ctrl + S (Windows,Linux)来保存修噶。Devtools 将会以打补丁的形式将整个 js 文件加入到 Chrome 的 JavaScript 引擎中去。

直接编辑js文件

上图中蓝框圈出来的就是编辑器栏。

实时编辑一个暂停的函数

注意:此功能自 Chrome 105 版本往上才可用。

当代码暂停的时候,你可以编辑当前函数然后实时应用修改,不过有以下限制:

  • 你只能编辑在 Call Stack 最顶上的函数(也即当前断点所在的函数——译者注)。

  • 调用栈中不能有对相同函数的递归调用(否则也相当于是修改了非当前调用栈的函数——译者注)。

💡 实时编辑暂停函数的真相是… 当你应用一个修改的时候,debugger 工具自动重新执行(和前面的「重新执行堆栈中的函数」一样)。因此,对重新执行函数的限制与实时编辑暂停函数并生效的限制是一样的。你不能重头执行 WebAssembly、async以及generator(迭代器)函数。

如果想实时编辑一个函数,你需要:

  1. 用断点暂停。

  2. 编辑断点所在的正在暂停的函数。

  3. 按下 Command/Control+S 来应用更改,debugger 将会自动重新执行该函数。

  4. 继续执行

在这个示例中,addend1addend2 变量在起始的时候有一个不正确的 string 类型。因此,字符串被错误的连接在一起而不是数字相加。为了修复此问题,在实时编辑的时候添加了parseInt() 函数。

搜索和替换在脚本中的文本

如果想要在脚本中搜索一段文本,你需要:

  1. 在代码源(Sources)-编辑器(Editor)栏中打开文件。

  2. 按下 Command+F(Mac)或者 Ctrl+F(Windows、Linux)来打开内置的搜索栏。

  3. 在搜索栏中,输入你想要查询的字符串:

    另外你还可以:

    • 点击 Aa 来匹配大小写,来让你的搜索大小写敏感。

    • 点击 .* 来使用正则匹配。

  4. 按下 Enter 键来执行搜索。可以按下 up/down 来跳转到下一个/上一个搜索结果。

搜索字符

如果想替换你搜索到的结果,你需要:

  1. 打开搜索栏,点击 A→B (手敲的不形象,看下面的图)即替换按钮来替换文本。

  2. 输入想要替换的文本,然后点击 Replce 或者 Replace all 即可。

按下替换按钮

禁用 JavaScript

具体参见:Disable JavaScript With Chrome DevTools(未翻译)。

- EOF -
本文最先发布在: 「译」官方指南系列:JavaScript 调试指南(一) - Xheldon Blog