调试JavaScript
本教程教你调试 DevTools 中的任何 JavaScript 问题的基本工作流程。请继续阅读,或者观看本教程的视频版本。
第一步:重现bug
- 找到一系列能够持续重现 bug 的操作始终是调试的第一步。
- 单击"打开演示"。演示将在一个新选项卡中打开
- 笔者注:演示是一个网页,需要科学上网,这里直接提供该网页的代码
js
<html>
<head>
<link rel="shortcut icon" href="/devtools-samples/favicon-96x96.png">
<title>Demo: Get Started Debugging JavaScript with Chrome DevTools</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
h1 {
font-size: 1.5em
}
input, button {
min-width: 72px;
min-height: 36px;
border: 1px solid grey;
}
label, input, button {
display: block;
}
input {
margin-bottom: 1em;
}
</style>
</head>
<body>
<h1>Demo: Get Started Debugging JavaScript with Chrome DevTools</h1>
<label for="num1">Number 1</label>
<input placeholder="Number 1" id="num1">
<label for="num2">Number 2</label>
<input placeholder="Number 2" id="num2">
<button>Add Number 1 and Number 2</button>
<p>1 + 2 = 12</p>
<script src="get-started.js"></script>
</body>
</html>
js
function onClick() {
if (inputsAreEmpty()) {
label.textContent = 'Error: one or both inputs are empty.';
return;
}
updateLabel();
}
function inputsAreEmpty() {
if (getNumber1() === '' || getNumber2() === '') {
return true;
} else {
return false;
}
}
function updateLabel() {
var addend1 = getNumber1();
var addend2 = getNumber2();
var sum = addend1 + addend2;
label.textContent = addend1 + ' + ' + addend2 + ' = ' + sum;
}
function getNumber1() {
return inputs[0].value;
}
function getNumber2() {
return inputs[1].value;
}
var inputs = document.querySelectorAll('input');
var label = document.querySelector('p');
var button = document.querySelector('button');
button.addEventListener('click', onClick);
- 在数字1文本框中输入5。
- 在数字2文本框中输入1。
- 单击添加数字1和数字2。按钮下面的标签写着5 + 1 = 51。结果应该是6。这就是你要解决的问题。
图1:5 + 1的结果是51,应该是6。
第二步: 熟悉资源面板的用户界面
DevTools 为不同的任务提供了许多不同的工具,比如更改 CSS、分析页面加载性能和监视网络请求。资源面板是调试 JavaScript 的地方。
- 按 Command + Option + i (Mac)或 Control + Shift + i (Windows,Linux)打开 DevTools。这个快捷键打开控制台面板。
图2. 控制台面板
- 点击资源标签。
图3.资源面板
源代码面板界面有3个部分:
图4. source 面板 UI 的3个部分
- 文件导航窗格:这里列出了页面请求的每个文件
- 代码编辑器窗格:在"文件导航器"窗格中选择一个文件后,该文件的内容将显示在这里
- 调试窗格:检查页面 JavaScript 的各种工具。如果" DevTools"窗口较宽,则此窗格将显示在"代码编辑器"窗格的右侧
第三步: 使用断点暂停代码
调试此类问题的一种常见方法是在代码中插入大量console.log()语句,以便在脚本执行时检查值。例如:
js
function updateLabel() {
var addend1 = getNumber1();
console.log('addend1:', addend1);
var addend2 = getNumber2();
console.log('addend2:', addend2);
var sum = addend1 + addend2;
console.log('sum:', sum);
label.textContent = addend1 + ' + ' + addend2 + ' = ' + sum;
}
console.log()方法虽然可以完成这项工作,但是断点可以更快地完成这项工作。断点允许您在代码执行的中间暂停代码,并检查该时刻的所有值。与console.log()方法相比,断点有几个优点:
- 使用Console. log(),您需要手动打开源代码,找到相关代码,插入Console. log()语句,然后重新加载页面,以便在Console中查看消息。使用断点,您甚至可以在不知道代码结构的情况下暂停相关代码。
- 在你的console.log()语句中,你需要显式地指定你想要检查的每个值。使用断点,DevTools会显示出所有变量在那个时刻的值。有时,有一些变量会影响代码,而您甚至没有意识到这些变量。
简而言之,与console.log()方法相比,断点可以帮助您更快地找到并修复bug。
如果你退后一步,想想这个应用程序是如何工作的,你可以做出一个有根据的猜测,错误的和(5 + 1 = 51)是在与添加1号和2号按钮相关联的单击事件侦听器中计算出来的。因此,您可能希望在单击侦听器执行时暂停代码。事件监听器断点可以让你做到这一点:
- 在JavaScript调试窗格中,单击事件侦听器断点以展开该部分。DevTools显示了一系列可扩展的事件类别,比如动画和剪贴板。
- 在鼠标事件类别旁边,单击"展开" 。DevTools显示了一个鼠标事件列表,比如单击和鼠标下拉。每个事件旁边都有一个复选框。
- 选中"单击"复选框。DevTools现在被设置为在任何单击事件监听器执行时自动暂停。 图5。启用"单击"复选框
- 回到演示中,再次单击Add Number 1和Number 2。DevTools暂停演示,并在源代码面板中突出显示一行代码。DevTools应该在这行代码上暂停:function onClick(){如果你在另一行代码上暂停,按恢复脚本执行 直到你在正确的行上停下来。注意:如果您在不同的行上暂停,您有一个浏览器扩展,它在您访问的每个页面上注册一个单击事件监听器。您被暂停在扩展的单击监听器。如果您使用Incognito Mode私有浏览(禁用所有扩展),您可以看到您每次都暂停在正确的代码行上。
事件监听器断点只是DevTools中可用的许多类型的断点之一。记住所有不同的类型是值得的,因为每种类型最终都能帮助您尽可能快地调试不同的场景。请参阅使用断点暂停代码以了解何时以及如何使用每种类型
第四步: 逐步完成代码
错误的一个常见原因是脚本以错误的顺序执行。逐步执行代码使您能够遍历代码的执行,一次一行,并准确地找出代码以不同于预期的顺序执行的位置。试一试:
- 在DevTools的Sources面板上,单击进入一个函数调用按钮 一步一步地执行onClick()函数。DevTools突出显示了下面这行代码:
js
if (inputsAreEmpty()) {
- 单击下一步函数调用。DevTools执行inputsAreEmpty()而不进入它。注意DevTools是如何跳过几行代码的。这是因为inputsAreEmpty()被求值为false,所以if语句的代码块没有执行。
这就是单步执行代码的基本思想。如果您查看get-start .js中的代码,您可以看到错误可能在updateLabel()函数的某个地方。您可以使用另一种类型的断点来暂停靠近错误可能位置的代码,而不是遍历每一行代码。
第五步:设置一个代码行断点
代码行断点是最常见的断点类型。当你有一个特定的代码行,你想暂停,使用行代码断点:
- 看看updateLabel()中的最后一行代码:
js
label.textContent = addend1 + ' + ' + addend2 + ' = ' + sum;
- 在代码的左边,您可以看到这一行代码的行号,也就是32。点击32。DevTools在32个图标上放了一个蓝色图标。这意味着这一行上有一个代码行断点。DevTools现在总是在这行代码执行之前暂停。
- 单击恢复脚本执行 . 脚本继续执行,直到第32行。在第29、30和31行,DevTools将addend1、addend2和sum的值打印到每行分号右侧。 图6。DevTools在32行的代码行断点上暂停
第六步:检查变量值
addend1、addend2和sum的值看起来可疑。它们用引号括起来,这意味着它们是字符串。这是解释bug原因的一个很好的假设。现在是收集更多信息的时候了。DevTools提供了很多工具来检查变量值。
方法一1: 面板范围
当您在一行代码上暂停时,Scope窗格会显示当前定义了哪些局部和全局变量,以及每个变量的值。如果可以的话,它还显示了闭包变量。双击一个变量值来编辑它。当您没有在一行代码上暂停时,Scope窗格是空的。
图7。面板范围
方法二:观察表达式
Watch Expressions选项卡允许您监视随时间变化的变量值。顾名思义,Watch表达式不仅仅局限于变量。您可以在Watch expression中存储任何有效的JavaScript表达式。试一试:
- 点击Watch选项卡.
- 单击Add表达式 .
- 输入 typeof sum.
- 按回车。DevTools显示类型sum: "string"。冒号右边的值是手表表达式的结果. 图8。Watch Expression窗格(右下),
在创建typeof sum Watch之后表达式。如果您的DevTools窗口很大,那么监视表达式窗格在右侧,在事件侦听器断点窗格的上方。
正如所怀疑的,当sum应该是一个数字时,它是作为一个字符串计算的。您现在已经确认了这就是bug的原因。
方法3:控制台
除了查看Console .log()消息外,您还可以使用Console来评估任意的JavaScript语句。在调试方面,您可以使用Console测试bug的潜在修复。试一试:
- 如果没有打开控制台抽屉,请按Escape键打开它。它会在DevTools窗口的底部打开。
- 在控制台中输入parseInt(addend1) + parseInt(addend2)。这条语句之所以有效,是因为您在addend1和addend2在作用域中的一行代码上暂停了。
- 按回车。DevTools计算该语句并输出6,这是您希望演示程序生成的结果。 图9。在计算parseInt(addend1) + parseInt(addend2)之后,控制台抽屉。
第七步:修复
你已经找到了bug的修复方法。剩下的工作就是通过编辑代码并重新运行演示来尝试修复。你不需要离开DevTools去应用修复。你可以直接在DevTools UI中编辑JavaScript代码。试一试:
- 单击恢复脚本执行 .
- 在代码编辑器中,替换第31行,
js
var sum = addend1 + addend2
改为
js
var sum = parseInt(addend1) + parseInt(addend2).
- 按Command+S (Mac)或Control+S (Windows, Linux)保存更改。.
- 单击禁用断点 . 它变成蓝色表示它是活跃的。当设置了这个时,DevTools会忽略您设置的任何断点
- 用不同的值试一下这个演示。演示程序现在可以正确计算了。
注意: 此工作流仅对浏览器中运行的代码应用修复。它不会为所有访问您页面的用户修复代码。为此,您需要修复服务器上的代码。
下一步
恭喜你!现在您知道了如何在调试JavaScript时充分利用Chrome DevTools。在本教程中学习的工具和方法可以为您节省无数的时间。
本教程只介绍了设置断点的两种方法。DevTools提供了许多其他方式,包括:
- 仅在您提供的条件为真时触发的条件断点。
- 捕获或未捕获异常上的断点。
- 当请求的URL匹配您提供的子字符串时触发的XHR断点。
请参阅使用断点暂停代码以了解何时以及如何使用每种类型。
有几个代码步进控件没有在本教程中解释。有关详细信息,请参见逐行代码。
使用断点暂停代码
使用断点暂停JavaScript代码。本指南解释了DevTools中可用的每种类型的断点,以及何时使用以及如何设置每种类型的断点。有关调试过程的实践教程,请参见开始在Chrome DevTools中调试JavaScript。
何时使用每种断点类型的概述
最著名的断点类型是代码行。但是,设置代码行断点的效率可能很低,特别是当您不知道具体在哪里查找,或者您正在处理一个大型代码库时。通过了解如何以及何时使用其他类型的断点,您可以在调试时节省时间。
断点类型 | 当你想暂停的时候使用这个 |
---|---|
代码行 | 在代码的精确区域上 |
条件代码行 | 在代码的一个确切区域上,但只有在其他条件为真时 |
DOM | 更改或移除特定 DOM 节点或其子节点的代码上 |
XHR | 当 XHR URL 包含字符串模式时 |
Event listener事件侦听器 | 在事件之后运行的代码上,如click, is fired. ,被解雇 |
Exception例外 | 在抛出已捕获或未捕获异常的代码行上 |
Function功能 | Whenever a specific function is called. |
行代码断点
当您知道需要研究的确切代码区域时,请使用代码行断点。DevTools总是在这行代码执行之前暂停。
在DevTools中设置代码行断点:
- 单击Sources选项卡
- 打开包含要中断的代码行的文件。
- 到代码行。
- 代码行左边是行号列。点击它。行号列顶部出现一个蓝色图标。
****图1:在第29行设置的代码行断点
代码中的代码行断点
从代码中调用调试器以在该行上暂停。这等同于代码行断点,只是断点是在代码中设置的,而不是在 DevTools UI 中设置的。
js
console.log('a');
console.log('b');
debugger;
console.log('c');
条件代码行断点
当您知道需要调查的代码的确切区域,但只希望在其他条件为真时暂停时,使用条件代码行断点。
若要设置条件代码行断点:
- 点击资源标签
- 打开包含要中断的代码行的文件
- 按代码行走
- 代码行左边是行号列。右键单击它
- 选择 Add conditional breakpoint。在代码行下面显示一个对话框
- 在对话框中输入您的条件
- 按回车键激活断点。一个橙色的图标出现在行号列的顶部
图2: 在第32行设置的条件代码行断点
管理代码行断点
使用"断点"窗格从单个位置禁用或删除代码行断点。
图3: Breakpoints 窗格显示了两个代码行断点: 一个在 get-started. js 的第15行,另一个在第32行
- 选中条目旁边的复选框以禁用该断点
- 右键单击一个条目以删除该断点
- 右键单击"断点"窗格中的任何位置以停用所有断点、禁用所有断点或删除所有断点。禁用所有断点相当于不检查每个断点。禁用所有断点会指示 DevTools 忽略所有代码行断点,并保留它们的启用状态,以便在重新激活它们时,它们处于与之前相同的状态
图4: Breakpoints 面板中的非激活断点被禁用和透明
DOM 更改断点
如果要在更改 DOM 节点或其子节点的代码上暂停,请使用 DOM 更改断点。
要设置 DOM 更改断点:
- 单击 Elements 选项卡
- 转到要设置断点的元素
- 右键单击元素
- 将鼠标悬停在"中断"上,然后选择子树修改、属性修改或节点删除
图5: 创建 DOM 变更断点的上下文菜单
XHR/Fetch 断点
当 XHR 的请求 URL 包含指定的字符串时,要中断时使用 XHR 断点。DevTools 暂停在 XHR 调用 send ()的代码行上。
注意:此特性也适用于Fetch请求。
当你看到你的页面正在请求一个不正确的 URL,并且你想要快速找到导致不正确请求的 AJAX 或者 Fetch 源代码时,这是一个很有帮助的例子。
设置 XHR 断点:
- 点击资源标签
- 展开 XHR"断点"窗格
- 单击添加断点
- 输入要断开的字符串。当该字符串出现在 XHR 的请求 URL 中的任何位置时,DevTools 会暂停
- 按回车确认
图6: 为 URL 中包含 org 的任何请求在 XHR 断点中创建 XHR 断点
事件侦听器断点
当您希望暂停在事件触发后运行的事件侦听器代码时,请使用事件侦听器断点。您可以选择特定事件(如单击)或事件类别(如所有鼠标事件)。
- 点击资源标签
- 展开"事件侦听器断点"窗格。 DevTools 显示事件类别的列表,如"动画"
- 检查其中一个类别,以便在触发该类别的任何事件时暂停,或者扩展该类别并检查特定事件
图7: 为 deviceorientation 创建一个事件侦听器断点
异常断点
当您希望在抛出已捕获或未捕获异常的代码行上暂停时,请使用异常断点。
- 点击资源标签。
- 单击"暂停异常"。启用时将变为蓝色。 * * 图8 * * : 暂停异常 * * 按钮
- (可选)检查暂停在捕获异常复选框,如果你也想暂停了例外,除了未捕获的。(可选)选中停顿在了例外
图9: 在未捕获的异常上暂停
函数断点
调用 debug (functionName) ,其中 functionName 是希望调试的函数,当您希望在调用特定函数时暂停时调用。可以将 debug ()插入到代码中(如 Console.log ()语句) ,或者从 DevTools 控制台调用它。Debug ()等价于在函数的第一行上设置代码行断点。
js
function sum(a, b) {
let result = a + b; // DevTools pauses on this line.
return result;
}
debug(sum); // Pass the function object, not a string.
sum();
确保目标函数在范围内
如果要调试的函数不在范围内,DevTools 将引发 ReferenceError。
js
(function () {
function hey() {
console.log('hey');
}
function yo() {
console.log('yo');
}
debug(yo); // This works.
yo();
})();
debug(hey); // This doesn't work. hey() is out of scope.
如果要从 DevTools 控制台调用 debug () ,那么确保目标函数在作用域中是很棘手的。这里有一个策略:
- 在函数作用域中的某个地方设置代码行断点。
- 触发断点。
- 在DevTools控制台中调用debug(),而代码仍然暂停在你的代码行断点上。