JavaScript 中的调试技巧

Posted by cl9000 on July 16, 2020

在事情成功之前,一切总看似不可能。——<纳尔逊·曼德拉>

作者:Ashish Lahoti
译者:cl9000
来源:https://codingnconcepts.com/javascript/debugging-tips-and-tricks/

在本教程中,我们将学习大量使用 Chrome DevTool 调试 JavaScript 代码的技巧和窍门,使用控制台 API 记录到控制台的不同方法,以及更多…

1. 对 alertsNo

1
alert("I will pop up in browser");

浏览器调试警报

当我们使用 alert() 调试值时,它会在浏览器中弹出,这很令人讨厌。尽量避免使用它们,因为还有其他方法可用于调试,我们将在后面介绍它们。

2. 浏览器调试器

大多数现代浏览器,如 ChromeFirefoxEdgeOperaSafari 都内置了对 JavaScript 调试的支持。Chrome 作为调试器通常是开发者的首选。

Chrome开发工具 Chrome Developer Tool

Chrome 的主菜单中打开 DevTool ,选择 更多工具 开发人员工具

快捷键 Mac Windows, Linux, Chrome OS
Elements Tab Command+Option+C Control+Shift+C
Console Tab Command+Option+J Control+Shift+J
Sources Tab Command+Option+I Control+Shift+I

Chrome Developer Tool

Chrome DevTools 一些最常用的图标:-

  • 使用 Inspect icon ⬉在页面中选择一个元素,然后在 Elements 选项卡中检查其 DOM 位置。您可以更新或删除一个 DOM 元素,检查在 DOM 元素上应用的 CSS,及在 Elements 选项卡中进行更多操作。
  • 使用设备 Device icon ⍇检查网站的响应性。它在你的浏览器页面上增加了一个额外的工具栏,你可以模拟不同设备的视图,例如手机(如iPhone,三星Galaxy, Nexus, LG,诺基亚,黑莓等),平板电脑(如iPad等)和笔记本电脑(有或没有触摸)。
  • Sources 选项卡下,您可以看到呈现该页面的所有 JavaScript 源代码。通常,源文件是压缩的,很难理解它们和应用断点。使用格式图标 {} 将这些压缩的文件格式化为人可读的格式。

3. 代码行断点

您可以使用断点暂停浏览器中的 JavaScript 代码。最著名的断点类型是代码行断点。

DevTools 中设置代码行断点:

  • 单击 Sources 选项卡。
  • 打开文件并转到要调试的代码行。
  • 您将看到代码左侧的行号列。点击它。行号旁边出现一个图标,表示设置了断点。
  • 如果要删除断点,请在相同的行号上再次单击。图标消失。

请注意,根据 Chrome 版本和操作系统(Windows或MacOS),DevTool 中的断点图标外观可能有所不同

有时,代码行断点的设置效率很低,尤其是在您不知道确切的位置或正在使用大型代码库的情况下。您可以通过知道如何以及何时使用其他类型的断点来节省调试时间。

断点类型 当您想暂停时使用此功能…
代码行 在确切的一行代码上。
条件代码行 在精确的代码行上,但仅当其条件为 true 时。
DOM 修改或删除特定 DOM节点及其子节点的代码。
XHR 关于XHR send()fetch() 方法当请求URL包含字符串模式时。
Event listener 在事件(如 click)触发后运行的代码上。
Exception 在引发捕获或未捕获异常的代码行上。
Function 每当调用特定 Function 时。

4. 使用’ debugger '语句从代码中设置断点

当浏览器中的调试器模式处于打开状态并且代码执行到达 debugger; 语句时,它会像该行代码断点一样在该行上暂停。

当您想在浏览器中调试代码的某个部分时,调试器语句非常有用。通常您会在浏览器开发人员工具中找到有问题的代码,并设置一个断点进行调试。有时不容易在浏览器中找到代码,在这种情况下,可以插入 debugger; 语句。

1
2
3
4
5
6
7
function hello(name) {
let phrase = `Hello, ${name}!`;

debugger; // <-- the debugger pause on this line

say(phrase);
}

5. 条件断点

当您知道需要调查的确切代码行时,可以使用条件代码行断点,但您只希望在某些其他条件为真时暂停。

  • 设置一个条件代码行断点:
  • 单击 Sources 选项卡。
  • 打开文件并转到要调试的代码行。
  • 您将看到代码左侧的行号列。右键单击它。
  • 选择 Edit breakpoint..,代码行下面会显示一个对话框。
  • 在对话框中输入条件。
  • Enter 激活断点。

Chrome DevTool Conditional Breakpoint

6. DOM更改断点

当您想暂停更改 DOM 节点或其子级的代码时,请使用 DOM 更改断点。

要设置 DOM 更改断点:

  • 单击 Elements 选项卡。
  • 转到要在其上设置断点的元素。
  • 右键单击该元素。
  • 将鼠标悬停在 Break on 上,然后选择 Subtree modifications, Attribute modificationsNode removal

DOM更改断点的类型 Types of DOM change breakpoints

  • Subtree modifications`: 当删除或添加当前选定节点的子节点,或更改子节点的内容时触发。不会在子节点属性更改时触发,也不会在当前选择的节点发生任何更改时触发。
  • Attributes modifications: 当在当前选择的节点上添加或删除属性时触发,或者当属性值更改时触发。
  • Node Removal: 当当前选择的节点被移除时触发。

7. XHR/Fetch 断点

如果您在 AJAX 请求中遇到错误,并且无法识别提交此请求的代码,那么 XHR 断点对于快速找到 AJAX 源代码非常有用。

AJAX 请求的 URL 包含指定的字符串时,XHR 断点将暂停代码的执行。AJAX send()fetch()方法支持 XHR 断点。

设置XHR断点:

  • 单击 Sources 选项卡。
  • 展开 XHR Breakpoints 窗口。
  • 点击 Add breakpoint
  • 输入要中断的字符串。当这个字符串出现在 XHR的请求 URL 中的任何地方时,DevTools会暂停。
  • Enter 确认。

8. 事件监听器断点

当您想在事件触发后运行的事件侦听器代码上暂停时,请使用事件侦听器断点。您可以选择特定的事件,如单击鼠标下的事件,或所有事件,如剪切、复制、粘贴下的剪贴板类别。

打开事件监听器断点:

  • 单击 Sources 选项卡。
  • 展开Event Listener Breakpoints窗格。DevTools显示了一个事件类别列表,如Animation, Canvas, Clipboard, Mouse等。
  • 要么检查 类别以包含该类别下的所有事件,要么展开该类别并检查☑某个特定事件。

9. 异常断点

Chrome 开发者工具允许你在抛出捕获或未捕获异常时暂停 JavaScript 代码的执行。当代码没有抛出错误和失败时,这一点特别有用。这使得您可以在创建 Error对象时检查应用程序的状态。

打开异常断点:

  • 单击 Sources选项卡。
  • 单击异常图标上的 Pause。启用时变为蓝色。
  • 如果您也想暂停已捕获异常的执行,请检查 ☑Pause on uncaught exceptions
  • 完成后记得关掉它们。

Chrome DevTool XHR, Event Listener and Exception Breakpoints

10. Function Breakpoint

调用 debug(functionName),其中 functionName 是您想要调试的函数,当您想在调用特定函数时暂停时。你可以在你的代码中插入debug()(就像Console.log()语句)或者从DevTools控制台调用它。Debug()等价于在函数的第一行设置代码行断点。

确保目标函数在范围内。如果要调试的函数不在范围内,DevTools 将抛出一个ReferenceError

1
2
3
4
5
6
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();

11. 黑盒脚本文件

您在 Chrome DevTool 中调试应用程序代码并逐行浏览代码时,调试器有时会跳转到您不关注的源文件中(例如第三方JS库)。我确定您在回到自己的应用程序代码之前已经经历了逐步浏览库代码的烦恼。

Chrome DevTool提供了将 JavaScript 文件添加为黑匣子的功能,以便调试器在逐步调试代码时不会跳入这些文件并忽略它们。

对脚本进行黑名单处理会怎样?

  • 从库代码引发的异常不会暂停(如果启用了“暂停暂停”),
  • 进入/退出/跳过库代码,
  • 事件侦听器断点不会在库代码中中断,
  • 调试器不会在库代码中设置的任何断点处暂停。

最终结果是您正在调试应用程序代码,而不是第三方资源。要将JavaScript文件黑匣子:

  • 单击 Main Menu > Settings 图标,或使用 F1快捷键打开设置
  • 单击 Blackboxing 菜单项。
  • 点击 Add Pattern… 按钮。
  • 在文本框中输入文件名或格式,然后单击 Add 按钮。
  • 选中 ☑ Blackbox content script 以启用黑盒。

Chrome DevTool Blackbox Script

12. 片段

通常,我们使用浏览器控制台执行和测试一些代码片段。有时,我们需要一次又一次地测试相同的代码。Chrome DevTools提供了保存代码段以供将来使用的功能。

要保存您的代码段,请执行以下操作:

  • 单击 Sources 选项卡。
  • Sources 选项卡的左侧面板中,单击 Snippets 子选项卡。
  • 单击 + New Snippet 创建一个新文件以保存您的代码段。
  • 当您打开代码段代码时。有用于格式化和执行代码的图标。

您可以使用摘要来存储由您或其他开发人员制作的常用调试代码脚本。保罗爱尔兰写了一些有用的片段- stopBefore.js,Grep.js您可以在您的DevTool段分别复制。可从控制台访问代码片段。让我们来看看它们:

  • storeBefore.js片段允许设置时触发某个函数被调用之前断点。例如,这将在document.getElementById调用该函数之前触发一个断点:
1
stopBefore(document, 'getElementById')
  • 所述grep.js代码段允许搜索的对象及其匹配给定的搜索条件的属性的原型链。例如,此指令将搜索与文档对象中的get匹配的所有属性:
1
grep(document, 'get');
  • 当访问给定的属性时,debugAccess.js片段允许触发断点。例如,每次调用document.cookie时,这都会触发一个断点:
1
debugAccess(document, 'cookie');

13. 打印多个值

大多数开发人员使用 console.log() 在浏览器控制台中调试值。它是调试之王,可以解决您的大多数调试问题。

您可能不知道的是,console.log() 可以通过提供逗号分隔的值来打印多个值,因此您无需自己连接多个值。

每个逗号 , 在值之间添加一个空格。

1
2
3
4
5
6
7
8
let x = 1;
console.log('x =', x);

let y = "hello";
let fruits = ['apple', 'banana', 'mango'];
var obj = {a: 1, b: 2};

console.log('x =', x, 'y =', y, 'fruits =', fruits, 'obj =', obj);

Output
x = 1
x = 1 y = hello fruits = (3) [“apple”, “banana”, “mango”] obj = {a: 1, b: 2}

您也可以使用Template Literal在单个字符串中组合多个值,如下所示

1
console.log(`x = ${x}, y = ${y}, fruits = ${fruits}, obj = ${obj}`);

14. 避免记录对象引用

不要使用console.log(obj),使用console.log(JSON.parse(JSON.stringify(obj)))替代。

当您使用 console.log() 记录阵列(或对象)并随后进行更新时。许多浏览器会向您显示数组(或对象)的最新状态,这可能会引起误解。

使用 console.log(JSON.parse(JSON.stringify(obj))) 确保记录,您正在打印数组(或对象)的副本,该副本将在打印时记录确切的状态。

让我们从下面的数组示例中了解这一点

1
2
3
4
5
6
let fruits =  ['apple', 'banana', 'mango'];

console.log(fruits);
console.log(JSON.parse(JSON.stringify(fruits))); //makes a copy of it

fruits.push('grapes');

Output
▼ (3) [“apple”, “banana”, “mango”]
0: "apple"
1: "banana"
2: "mango"
3: "grapes"
length: 4
proto: Array(0)
▼ (3) [“apple”, “banana”, “mango”]
0: "apple"
1: "banana"
2: "mango"
length: 3
proto: Array(0)

我们看到 console.log() 显示长度为4的数组的最新状态,而在记录时其长度为3。

让我们看一下对象示例

1
2
3
4
5
6
let person =  { name: 'adam', age: 21, gender: 'male' };

console.log(person);
console.log(JSON.parse(JSON.stringify(person))); //makes a copy of it

person.married = 'NO';

Output
▼ {name: “adam”, age: 21, gender: “male”}
age: 21
gender: "male"
married: "NO"
name: "adam"
proto: Object
▼ {name: “adam”, age: 21, gender: “male”}
age: 21
gender: "male"
name: "adam"
proto: Object

我们看到 console.log() 显示了对象的最新状态,包括在记录时丢失的 married 字段。

15. 以JSON格式打印DOM元素

使用 console.log()DOM 元素打印为 HTML 元素树结构。相反,我们可以使用 console.dir() 来查看 JSON 中的 DOM 元素(如树结构)。

1
2
console.log(document.body);
console.dir(document.body);

16. 打印构造函数的原型方法

console.dir() 是打印构造函数的内部属性非常有用,如原型方法。

在下面的示例中,我们看到 console.log() 仅显示构造函数的名称,而使用 console.dir() ,我们可以看到 Array 函数的所有原型方法。

1
2
console.log(Array);
console.dir(Array);

Output
ƒ Array() { [native code] }
▼ ƒ Array()
arguments: (…)
caller: (…)
length: 1
name: "Array"
▼ prototype: ƒ ()
length: 0
➤ constructor: ƒ Array()
➤ concat: ƒ concat()
➤ find: ƒ find()
➤ findIndex: ƒ findIndex()
➤ lastIndexOf: ƒ lastIndexOf()
➤ pop: ƒ pop()
➤ push: ƒ push()
➤ reverse: ƒ reverse()
➤ slice: ƒ slice()
➤ sort: ƒ sort()
➤ splice: ƒ splice()
➤ includes: ƒ includes()
➤ indexOf: ƒ indexOf()
➤ join: ƒ join()
➤ toString: ƒ toString()

17. 打印功能的关闭

所述 console.dir() 是打印功能的内部属性,如示波器和封闭件相当有用的。

在下面的示例中,我们看到 console.log() 仅使用 console.dir() 打印函数的签名,我们可以看到函数的原型方法,作用域以及最重要的是闭包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var outerFunc  = function(c){ 
var a = 1;
var innerFunc = function(d) {
var b = 2;
var innerMostFunc = function(e) {
return a + b + c + d + e;
}
return innerMostFunc;
}
return innerFunc;
}

// print innerMostFunc
console.log(outerFunc(3)(4));
console.dir(outerFunc(3)(4));

Output
ƒ (e) {
return a + b + c + d + e;
}
▼ ƒ innerMostFunc©
length: 1
name: "innerMostFunc"
arguments: null
caller: null
➤ prototype: {constructor: ƒ}
proto: ƒ ()
[[FunctionLocation]]:
▼ [[Scopes]]: Scopes[3]
➤ 0: Closure (innerFunc) {d: 4, b: 2}
➤ 1: Closure (outerFunc) {c: 3, a: 1}
➤ 2: Global {parent: Window, opener: null, top: Window, length: 1, frames: Window, …}

18. 打印对象的内部属性

一些浏览器在使用 console.log() 时打印对象的字符串化版本,而在使用 console.dir() 时打印对象的JSON 树。预先打印在浏览器(例如chrome)中并没有太大区别,这两种方法都可以打印对象JSON树
我们发现在 Chrome浏览器中同时使用两种方法打印对象时,没有什么不同

Output
▼ {a: 1, b: 2}
a: 1
b: 2
proto: Object
▼ Object
a: 1
b: 2
proto: Object

19. 将对象打印为表格

所述 console.table() 方法是打印一个大的数据集以表格形式容易地可视化它非常有用。此方法还提供了从那些大型数据集中打印一些字段的功能。当在浏览器中呈现为表格时,浏览器提供了对表格的列进行排序的功能。

让我们看一下以表格格式记录大型JSON的示例,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
var personDetails = [
{
"_id": "5edbbb78633118f455e877fb",
"index": 0,
"guid": "30dd1d2c-5083-4165-8580-5ae734cd0d12",
"isActive": true,
"balance": "$1,778.03",
"picture": "http://placehold.it/32x32",
"age": 26,
"eyeColor": "blue",
"name": {
"first": "Anderson",
"last": "Sargent"
},
"company": "MAZUDA",
"email": "anderson.sargent@mazuda.com",
"phone": "+1 (839) 437-3851",
"address": "235 Ashland Place, Chautauqua, Minnesota, 3487",
"about": "Pariatur nisi cillum culpa aliquip mollit veniam. Laboris in minim non dolor ut deserunt ex sit occaecat irure consequat pariatur esse. Cillum velit dolore enim non enim ipsum aliqua veniam fugiat adipisicing magna mollit occaecat.",
"registered": "Saturday, April 8, 2017 3:02 AM",
"latitude": "26.03084",
"longitude": "-74.869342",
"tags": [
"labore",
"nulla",
"ea",
"qui",
"sunt"
],
"range": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
"friends": [
{
"id": 0,
"name": "Coleman Nunez"
},
{
"id": 1,
"name": "Foley Curry"
},
{
"id": 2,
"name": "Kara Glass"
}
],
"greeting": "Hello, Anderson! You have 5 unread messages.",
"favoriteFruit": "apple"
}
]

console.table(personDetails);
console.table(personDetails, ["age", "eyeColor"]); // print few fields

以下表格格式的输出来自chrome浏览器

20. 执行代码所需的打印时间

可以打印由执行代码,通过使用所用的时间)console.time(前和console.timeEnd() 该特定的代码之后。很少的指针:

  • console.time()console.timeEnd() 方法应具有相同的标签。
  • 您还可以通过为方法分配不同的标签来设置多个计时器。
  • console.time()console.timeEnd()Console API 的一部分(就像console.log()一样)

让我们看看它是如何工作的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
console.time('Timer1');
console.time('Timer2');

var items = [];

for(var i = 0; i < 100000; i++){
items.push({index: i});
}

console.timeEnd('Timer1');

for(var j = 0; j < 100000; j++){
items.push({index: j});
}

console.timeEnd('Timer2');

Output
Timer1: 13.088134765625ms
Timer2: 26.070517578125ms

21. 打印方法执行的堆栈跟踪

可以使用 console.trace() 将方法执行流的堆栈跟踪打印到控制台。数指针:

  • console.trace() 兼容 Chrome DevToolssnippet 特性。
  • Console .trace()Console API 的一部分(就像Console.log()一样)

让我们看看它是如何工作的:

1
2
3
4
5
const first = () => { second(); };
const second = () => { third(); };
const third = () => { fourth(); };
const fourth = () => { console.trace("The trace"); };
first();

您可以在控制台输出中获得 @ file_name:line_number,单击它可以导航到源文件。

Output
▼ The trace
fourth @ test:4
third @ test:3
second @ test:2
first @ test:1
(anonymous) @ test:5

22. 使用单元测试框架

有许多针对JavaScript的第三方单元测试框架,它们具有自己的理念和语法。以下是最广泛使用的JavaScript测试框架:

  • Mocha是功能丰富的框架,可在 Node.js 和浏览器中运行,从而使异步测试变得简单而有趣。Mocha测试按顺序运行,从而可以灵活,准确地报告,同时将未捕获的异常映射到正确的测试用例。
  • JESTFacebook 维护的流行框架。它是基于React的应用程序的首选框架,因为它需要零配置。它非常适合使用Babel,TypeScript,Node,React,Angular和Vue的项目。
  • Jasmine是一个行为驱动的测试框架。它没有外部依赖性。它不需要DOM。而且它的语法清晰明了,因此您可以轻松编写测试。
  • QUnit是一个功能强大,易于使用的 JavaScript 单元测试框架。它由jQuery,jQuery UIjQuery Mobile项目使用,并且能够测试任何通用 JavaScript代码

参考

关注【公众号】,了解更多。
关注【公众号】,了解更多。
关注【公众号】,了解更多。



支付宝打赏 微信打赏

赞赏一下 坚持原创技术分享,您的支持将鼓励我继续创作!