调试JavaScript的详细指南

116 阅读11分钟

每天,我们醒来,吃了一顿丰盛的早餐,走向电脑,坐在那里,编写完美的代码,做我们想要的事情。

然后我们就会醒来。

这将是理想的,对吗?但这是一个梦。

尽管你可以变得很好,但你不可能写出没有错误的代码。代码有bug。根据定义。

一个bug是你在着手写代码时没有看到或预见到的问题。

一个bug可能在你向用户发布程序时才被发现,这是最糟糕的情况。

一个错误可以在你测试程序时被发现,它甚至可能发生在事情--以前工作得很完美--因为你改变了一行而开始中断。

这些被称为回归错误。

作为开发人员,错误是我们日常工作的一部分,但我们的工作是尽可能地减少它们。

你如何处理bug?

首先,通过仔细思考你的程序应该如何工作,甚至在你写下一行代码之前,尽可能地避免它们。

然后,通过分析你写的每一行代码,找出可能的问题或副作用或未考虑的事情。

但事情总是在雷达下溜走。没有人是故意引入bug的。很多时候,只有当程序在生产中被客户使用时,bug才会出现。

而且......一旦你知道有一个bug,你如何解决它?

好吧,最困难的部分总是确定错误来自哪里。

然后,第二难的部分是弄清楚为什么会发生这个错误。

一旦你知道了以上所有的情况,解决这个bug一般来说是很容易的。

一般来说,我们可以做两件事来解决这个bug。

一种技术是非常基本的,包括尝试找出状态的值(变量的内容),以及程序的流程,并将这些变量打印到日志中,或打印到程序的输出中。

弄清楚错误可能发生在哪里

调试是程序员活动的核心技能之一。

有时我们做了最好的工作,但程序却不能正常工作,例如,它崩溃了,它只是很慢,或者它打印了错误的信息。

当你写的程序表现得不像你期望的那样时,你会怎么做?

你开始调试它。

第一步总是要看看正在发生什么,并试图确定问题来自哪里。

是环境中的问题吗?

是你给程序的输入的问题吗?

是由于内存使用量太大导致的一次性崩溃?

还是在你每次运行时都会出现这种情况?

这些都是在找出问题时开始向正确方向前进的关键信息。

一旦你对错误的来源有了一定的了解,你就可以开始检查代码的特定部分。

最简单的调试方法,至少在工具方面,是通过阅读你写的代码。大声读。

从我们自己的声音中听到一些神奇的东西,这在你静静地阅读时是不会发生的。

很多时候,我就是通过这种方式发现问题的。

在这一步之后,是时候使用一些工具了。

你第一次接触alert()console.log()

如果阅读代码没有发现任何问题,那么下一个合乎逻辑的步骤就是开始在你的代码中加入几行可以说明问题的内容。

在JavaScript前端代码中,你经常会做的是使用alert()console.log

考虑一下这一行。

const a = calculateA()
const b = calculateB()
const result = a + b

由于某些我们不知道的原因,代码的最终结果没有被正确计算出来,所以我们在计算结果之前先加入alert(a)alert(b)

浏览器在执行该代码时,会打开两个警报面板。

const a = calculateA()
const b = calculateB()
alert(a)
alert(b)
const result = a + b

如果你传递给alert() 的是一个字符串或一个数字,这就可以了。

一旦你有一个数组或一个对象,事情就开始变得太复杂了,alert() ,你可以使用console.log()

const a = calculateA()
const b = calculateB()
console.log(a)
console.log(b)
const result = a + b

该值被打印在浏览器开发工具的JavaScript控制台中。

检查对象

假设我们有这个对象car ,但我们不知道它的内容,我们想检查它。

const car = {
  color: "black",
  manufacturer: "Ford",
  model: "Fiesta",
}

我们有几种方法可以做到这一点。

console.log

console.log(car)

console.dir

console.dir(car)

在Node.js中,你可以使用colors 属性来在终端呈现颜色。

console.dir(car, { colors: true })

JSON.stringify()

这将把该对象打印成字符串表示。

JSON.stringify(car)

通过添加这些参数。

JSON.stringify(car, null, 2)

你可以让它打印得更漂亮。最后一个数字决定了缩进中的空格数量。

JSON.stringify() 具有在控制台之外工作的优势,因为你也可以在屏幕上打印对象。

使用一个循环来迭代属性

for...in 循环对于打印一个对象的所有属性是很方便的,以这种方式使用。

const inspect = (obj) => {
  for (const prop in obj) {
    if (obj.hasOwnProperty(prop)) {
      console.log(`${prop}: ${obj[prop]}`)
    }
  }
}

inspect(car)

使用浏览器调试器

能够调试那些不按你的期望工作的程序是非常重要的。

在找出错误的根源时,有一个工具对你帮助很大,那就是使用调试器。

调试器是一种工具,它可以由你的编程语言编译器提供,也可以由围绕它建立的工具提供。

例如,微软的Visual Studio Code编辑器提供了一个JavaScript调试器。

另一个调试器是在浏览器里面提供的。

使用调试器,你将能够在任何时候停止程序的运行,观察变量的内容,执行任何你想要的代码,并一步一步地执行程序中的每一行代码。

在浏览器中,在你的代码中加入debugger 语句将暂停浏览器渲染页面,并将启动调试器。

调试器是浏览器开发工具中最强大的工具,它可以在 ""面板中找到。

屏幕的顶端部分显示了文件导航器。

你可以选择任何文件并在右边检查它。这对于设置断点是非常重要的,我们将在后面看到。

下面的部分是实际的调试器。

断点

当浏览器加载一个页面时,JavaScript代码被执行,直到遇到一个断点。

在这一点上,执行被停止,你可以检查所有关于你的运行程序。

你可以检查变量的值,并逐行恢复程序的执行。

但首先,什么是断点?简单来说,断点就是在你的代码中加入一条breakpoint 指令。当浏览器遇到它时,它就会停止。

这是开发时的一个好选择。另一个选择是在 "源 "面板中打开文件,点击你想添加断点的那一行的数字。

再次点击该断点会将其删除。

添加断点后,你可以重新加载页面,当代码找到断点时就会停止在该执行点。

在你添加断点时,你可以在断点面板上看到form.js ,在第7 行有断点。你可以在那里看到你所有的断点,并暂时禁用它们。

也有其他类型的断点。

  • XHR/fetch断点:当任何网络请求被发送时被触发
  • DOM断点:当一个DOM元素发生变化时被触发
  • 事件监听器断点:当某些事件发生时被触发,比如鼠标点击。

范围

在这个例子中,我在一个事件监听器中设置了一个断点,所以我必须提交一个表单来触发它。

现在,所有在范围内的变量都被打印出来,并附有各自的值。你可以通过双击这些变量来编辑它们。

观察变量和表达式

范围面板的右边有一个观察面板。

它有一个+ 按钮,你可以用它来添加任何表达式。例如,添加name 将打印name 变量值,在这个例子中Flavio 。你可以添加name.toUpperCase() ,它将打印FLAVIO

恢复执行

现在脚本都被停止了,因为断点停止了执行。

在 "暂停执行断点 "的横幅上方有一组按钮,可以让你改变这一状态。

第一个是蓝色的。点击它可以恢复正常的脚本执行。

第二个按钮是跨步,它恢复执行直到下一行,然后再次停止。

下一个按钮执行步入操作:进入正在执行的函数,让你去了解它的细节。

step out则相反:回到调用这个函数的外部函数。

这些是在调试期间控制流程的主要方法。

编辑脚本

在这个devtools屏幕上,你可以编辑任何脚本,也可以在脚本停止执行时编辑。只要编辑文件,然后按Mac上的cmd-S或Windows/Linux上的ctrl-S。

当然,除非你在本地工作并在devtools中设置了工作空间,否则所做的修改不会持久化到磁盘上,这是一个更高级的话题。

检查调用堆栈

调用堆栈对于查看你深入到JavaScript代码的多少个函数层是非常好的。它也可以让你通过点击每个函数名称在堆栈中向上移动。

打印堆栈跟踪

在某些情况下,打印一个函数的调用堆栈跟踪是很有用的,也许可以回答你是如何达到这部分代码的?

你可以使用console.trace()

const function2 = () => console.trace()
const function1 = () => function2()
function1()

记录不同的错误级别

正如我们之前所看到的,console.log 是在Console中打印信息的好帮手。

现在我们将发现另外三个方便的方法,它们将帮助我们进行调试,因为它们隐含地表明了各种级别的错误。

首先,console.info()

正如你所看到的,旁边印有一个小的'i',表明该日志信息只是一个信息。

第二,console.warning()

印出了一个黄色的感叹号。

如果你激活控制台过滤工具栏,你可以看到控制台允许你根据类型过滤信息,所以区分信息真的很方便,因为例如我们现在点击'警告',所有打印出来的不是警告的信息都会被隐藏。

第三个函数是console.error()

这个函数与其他函数有点不同,因为除了打印一个红色的X,明确指出有一个错误之外,我们还有产生错误的函数的完整堆栈跟踪,所以我们可以去尝试修复它。

在导航过程中保留日志

控制台信息在每次页面导航时都会被清除,除非你在控制台设置中勾选保留日志

对控制台信息进行分组

控制台信息可能会越来越大,当你试图调试一个错误时,噪音会让人喘不过气来。

为了限制这个问题,Console API提供了一个方便的功能。对控制台信息进行分组。

让我们先做一个例子。

console.group('Testing the location')
console.log('Location hash', location.hash)
console.log('Location hostname', location.hostname)
console.log('Location protocol', location.protocol)
console.groupEnd()

正如你所看到的,Console创建了一个组,在那里我们有日志信息。

你可以做同样的事情,但是输出一个折叠的消息,你可以根据需要打开,以进一步限制噪音。

console.groupCollapsed('Testing the location')
console.log('Location hash', location.hash)
console.log('Location hostname', location.hostname)
console.log('Location protocol', location.protocol)
console.groupEnd()

很好的一点是,这些组可以嵌套,所以你可以最终做

console.group('Main')
console.log('Test')
console.group('1')
console.log('1 text')
console.group('1a')
console.log('1a text')
console.groupEnd()
console.groupCollapsed('1b')
console.log('1b text')
console.groupEnd()
console.groupEnd()

黑盒脚本

很多时候,你和库一起工作,你不想 "介入",你信任他们,你不想在调用栈中看到他们的代码,例如。就像上面提到的validator.min.js ,我用它来验证电子邮件。

我相信它做得很好,所以我可以在调用堆栈中右键单击它,然后按黑盒脚本。从此以后,就不可能再踏入这个脚本代码了,你可以愉快地只在自己的应用程序代码上工作。

使用浏览器的devtools来调试Node.js

由于Node.js是建立在Chromev8的同一引擎上,你可以将2者联系起来,使用Chrome DevTools来检查Node.js应用程序的执行情况。

打开你的终端并运行

node --inspect

然后在Chrome中输入这个URL。about://inspect.

点击Node目标旁边的Open dedicated DevTools for Node链接,你就可以在浏览器DevTools中访问Node.js了。

请确保你点击这个链接,而不是下面的检查链接,因为当我们重新启动Node.js实例时,该工具会自动重新连接到Node.js实例--非常方便!