八、JavaScript 和调试

到目前为止,我已经讨论了如何安装软件以及使用 JavaScript 时需要知道的一些重要事情。在很大程度上,JavaScript 是您在浏览器中使用的东西。

既然如此,如果你写的代码有效,结果就会显示在浏览器中。更重要的是,如果结果不工作,你需要一种方法来看看哪里出错了。

问题就变成了,“我如何在浏览器中看到我的代码发生了什么?”令人高兴的是,浏览器内置了调试工具。本章将介绍一些在浏览器中调试代码时可以使用的工具。

火狐有专门针对开发者的浏览器,火狐开发者版( www.mozilla.org/en-US/firefox/developer/ )有工具,是那个浏览器特有的。

在撰写本文时,谷歌的 Chrome 浏览器仍然是最受欢迎的。因此,我将使用该浏览器中的开发工具作为参考。

开发人员工具由不同的面板组成。虽然我不会详细讨论每个面板,但是有些面板是针对特定任务的。这些面板包括

  • 设备模式

  • 元素面板

  • 控制盘

  • 来源面板

  • 网络面板

  • 性能面板

  • 内存面板

  • 应用面板

  • 安全面板

控制台面板

在前面的章节中,您使用了控制台面板。这允许您直接在浏览器中执行 JavaScript 并返回结果。您还可以调用加载到浏览器中的 JavaScript 文件中定义的函数。

因为您过去曾经使用过控制台,所以这是一个很好的起点。要打开浏览器控制台,右键单击页面并选择检查选项。这将打开开发人员工具。如果要使用键盘命令,请选择 Command + Option + J(在 Mac 上)或 Control + Shift + J(在 Windows 上)。见图 8-1

img/313453_3_En_8_Fig1_HTML.jpg

图 8-1

显示控制台面板

控制台有自己的 API(应用编程接口)。控制台的目的是让您了解正在执行的代码。

到目前为止,您只是使用了console.log方法让浏览器为您打印出结果。这很有帮助,但不是使用控制台帮助您了解浏览器中发生的事情的唯一方式。

其中一些函数是不言自明的。比如console.clear()会清除控制台里的一切。

使用clear方法的唯一例外是保存日志设置被取消。这将在页面刷新之间保留您在控制台中所做工作的历史记录。你可以通过点击开发者工具窗口右边的图标来找到它。见图 8-2

img/313453_3_En_8_Fig2_HTML.jpg

图 8-2

显示控制台设置

console 对象有几个方法,这些方法有助于理解代码在执行时发生了什么。例如,count方法跟踪同一个源调用一个函数的次数。在这种情况下,单击按钮是一样的。见清单 8-1

8-1.html

<button id="myButton">Click Me</ button >

8-1.js

function myCount(evt){
   console.count(evt);
}
document.addEventListener("DOMContentLoaded", () => {
   document.querySelector("#myButton").addEventListner("click", myCount);

});

//result [object MouseEvent]: 1..2..3

Listing 8-1Using the count Method as Part of the console API Class

这个例子等待DOMContentLoaded事件发生。然后,它查看文档,通过 ID 找到按钮,并为其分配一个事件侦听器。

当按钮被点击时,count方法跟踪与按钮点击相关的函数被调用的次数。所以,不用在代码中创建变量,你可以看到这个函数被调用了一定的次数。

dir方法获取一个对象并在控制台中打印出来。在这里,您可以单击查看为对象显示的所有属性和方法,包括任何子属性和方法。

清单 8-2 从清单 8-1 中提取代码,并使用dir方法来实现不同的结果。

8-2.html

<button id="myButton">Click Me</ button >

8-2.js

function myCount(evt){
   console.dir(evt);
}
document.addEventListener("DOMContentLoaded", () => {
   document.querySelector("#myButton").addEventListner("click", myCount);

});

Listing 8-2Using the dir Method as Part of the console API Class

这段代码让您可以查看MouseEvent附带的所有属性和方法。这类似于使用console.log,在这里您可以看到您正在处理的对象的所有属性和方法。见图 8-3

img/313453_3_En_8_Fig3_HTML.jpg

图 8-3

显示 MouseEvent 对象的方法和属性

一个类似于调用console.log的方法是dirxml。该方法返回一个对象,但它不是将所有属性和方法显示为一个 JavaScript 对象,而是将其显示为一个基于标记的 XML 文档。

清单 8-3 显示了dirdirxml方法。在本例中,函数myCount首先显示dirdxml的结果,然后显示dir的结果。参见图 8-4

img/313453_3_En_8_Fig4_HTML.jpg

图 8-4

从控制台 API 显示 dir 和 dirxml 方法的结果

8-3.html

<button id="myButton">Click Me</ button >

8-3.js

function myCount(evt){
   console.dirxml(document);
  console.dir(document);
}
document.addEventListener("DOMContentLoaded", () => {
   document.querySelector("#myButton").addEventListner("click", myCount);
});

Listing 8-3Using Both the dir and dirxml Methods

8-4 显示第一级结果以 HTML/XML 格式显示文档对象。你可以看到所有标签的层次结构,并点击它们。

第二层显示了文档对象的所有方法和属性。在这种情况下,它更像是在查看一个 JavaScript 对象,这比第一个例子更难理解层次结构。

有时您可能有一些事情想要记录,但是仅仅使用log方法意味着您需要在控制台中滚动结果来找到它们。这就是你可以结合使用groupgroupEnd方法的地方。您可以将日志的结果组合在一起。需要记住的是,要结束这个组,需要调用groupEnd方法。

清单 8-4 展示了如何获取一个对象的属性并将它们组合在一起。这为您在控制台中查看结果提供了更多的上下文。

8-4.js

let insocAlbums = {'first': 'Information Socieity', 'second'': 'Hack', 'thrid': 'Peace and Love Inc.'};

function groupBand(albums){
   console.group("Album List");
   console.log('first:' , albums.first);
   console.log('second:' , albums.second);
   console.log('thrid:' , albums.third);
   console.groupEnd();

}
document.addEventListener("DOMContentLoaded", () => {
      groupBand(insocAlbums);
});

Listing 8-4Using the group and groupEnd Methods to Group the Results of the log Method

在本例中,您有一个名为insocAlbums的对象。您希望看到该对象的属性组合在一起。下一个函数groupBand查看传递过来的对象的单个属性,并在浏览器控制台中打印出来。

这里重要的区别在于,您用一个标题显式地将这些项目分组,并指定该组的结尾。图 8-5 显示了结果。

img/313453_3_En_8_Fig5_HTML.jpg

图 8-5

使用控制台 API 的 group 和 groupEnd 方法的结果

现在,您可以从分组在一起的对象中看到您感兴趣的所有项目。您可以单击组标题并展开列表。

我将在本节中介绍的最后一种方法是table方法。顾名思义,它在控制台上一个漂亮的表格中显示结果。让我们以最后一个例子为例,将其显示为表格。参见清单 8-5

8-5.js

let insocAlbums = {'first': 'Information Socieity', 'second'': 'Hack', 'thrid': 'Peace and Love Inc.'};

function groupBand(albums){
       console.table(albums);
}
document.addEventListener("DOMContentLoaded", () => {
      groupBand(insocAlbums);
});

Listing 8-5The table Method

方法在一个格式良好的表格中显示一个对象的所有属性和方法。图 8-6 显示了一个例子。

img/313453_3_En_8_Fig6_HTML.jpg

图 8-6

使用控制台 API 的表方法的结果

您可以看到结果与使用group方法得到的结果非常相似。其中一个好处是,你不需要做任何工作来实现这一点。

这是您可以通过控制台面板使用的一些方法的概述。在很大程度上,这些方法帮助您查看正在使用的对象的值。

有时,您可能希望更好地了解代码在浏览器中是如何执行的,以及代码执行时变量的值是如何变化的。控制台面板很有帮助,但是还有一个面板可以提供更多的细节。

源代码面板可以帮助你做一些事情,比如暂停程序,让你在代码运行时看到代码发生了什么。让我们看看源代码面板。

来源面板

“源代码”面板有三个部分。首先是文件导航器。它可以让你检查属于你的网站的任何文件。

一旦从文件导航器中选择了一个文件,您就可以使用代码编辑器来查看该文件的内容,并做出一些将实时反映在浏览器中的更改。

第三个面板是 JavaScript 调试面板。这个面板允许你做一些事情,比如观察一段时间内变量的值。当程序执行时,您还可以看到当前范围内所有变量的值。

在下一个示例中,您将使用与之前类似的东西。基本格式是一样的。会有一个按钮可以点击,但不同的是这个按钮会调用一个 web 服务并返回一些数据。在这里,您可以使用“源代码”面板来了解浏览器是如何执行代码的。

首先,你需要理解代码。看一下清单 8-6

8-6.js

let jsonResults;
function getData(){
       fetch('https://jsonplaceholder.typicoce.com/todos/)
       .then(response => response.json())
      .then(json => saveData(json)));
}

functdion saveData(json){
    jsonResults = json;
    console.log(jsonResults);
}
document.addEventListener("DOMContentLoaded", () => {
     document.querySelector("#myButton").addEventListner("click", getData);

});

Listing 8-6Using the fetch method to return data from a webservice

在本例中,您正在以不同的方式做一些小事情。点击按钮后,调用getData功能。这个函数调用fetch方法。

fetch方法调用一个远程服务,在您的例子中,这个服务返回一些 JSON 数据。

当结果被返回到浏览器时,你做一些叫做方法链接的事情。这是从一个方法直接调用另一个方法的能力。如果您使用过 JQuery,这是一种常见的做法。

方法返回一个叫做承诺的东西。承诺给了你等待价值的能力。

在这种情况下,因为您正在调用远程服务,所以结果可能需要一些时间来返回值。一个承诺会让你知道数据什么时候被返回给浏览器。

当结果返回时,它可以链接到一个then方法,该方法将告诉浏览器运行一个函数,该函数将结果解析为应用可以使用的 JSON 数据。然后链接另一个函数,将数据传递给另一个名为saveData的函数。

面试问题

什么是承诺,它是如何运作的?什么是方法链?

saveData函数获取传递给它的 JSON 并保存到变量jsonResults

您可以使用“源代码”面板中的一些选项来检查这是如何处理的。一种方法是设置所谓的断点。断点停止代码的执行;这使得调试环境能够及时地向您提供关于代码当时正在发生什么的信息。

如果浏览器是展开的,左侧应该有 JavaScript 调试工具。在称为事件侦听器断点的部分,您可以查看鼠标事件。

通过单击箭头,可以展开列表,详细显示可以分配给断点的鼠标事件的类型。参见图 8-7

img/313453_3_En_8_Fig7_HTML.jpg

图 8-7

JavaScript 调试窗格

通过选择点击断点,点击按钮将在getData函数的第一行暂停程序的执行。此时,浏览器正在等待您使用调试工具来告诉它下一步应该做什么。

调试工具可以通知您到这个断点为止发生的所有事情。查看 Scope 面板,您可以看到变量this的值。

设置断点的另一种方法是直接在代码中设置。使用当前的例子,如果您想在函数执行的中途知道一个变量的值,您可以点击saveData函数。通过这样做,您可以看到类似于jsonResults变量的值。

如果不希望有两个断点,请确保关闭 MouseEvent 断点。

在函数内部设置断点后,请确保刷新浏览器,然后单击按钮。程序将再次停止,这一次正好是断点被设置的地方。查看范围面板,您现在可以看到属性this的当前值现在是window

作用域面板上方是调用堆栈面板。该面板显示所有执行功能的顺序。从下往上,您会看到首先调用的是getData函数,然后移动到当前的saveData函数。

面板的最顶端是手表面板。它允许您添加任何有效的 JavaScript 表达式,包括变量,并观察其值随时间的变化。

与设置断点不同,在设置断点的情况下,您只能在特定时刻获取值,而 Watch 允许您查看值,即使它们发生了变化。打开这个面板,点击加号按钮,输入"jsonResults"

在应用的第 11 行的console.log方法处添加一个断点。您的应用目前停在第 10 行,这是您的原始断点的位置。您需要一种方法来移动到下一个断点,这样您就可以看到jsonResults的值发生了变化。这是介绍使用调试器来遍历代码的能力的好时机。见图 8-8

img/313453_3_En_8_Fig8_HTML.jpg

图 8-8

deveoper 工具使您能够浏览代码,让您选择是浏览一个函数还是转到下一个函数

这些工具从左到右展示了在设置断点时如何浏览代码。这些工具位于您一直使用的所有面板的顶部。

第一个图标允许您继续执行脚本。它只是继续执行需要执行的下一部分代码。例如,当使用断点时,第一个按钮允许您继续或暂停代码的执行。

在当前示例中,如果您想要移动到下一个断点,您可以单击此按钮,它会将您带到下一行代码。浏览器将在下一个断点处停止,并显示jsonResults的更新值。

如果您的函数更复杂,您可能希望浏览该函数,以便更好地理解该函数的工作内容。下一个示例将使用调试面板中的一些其他工具来帮助您逐步完成更复杂的函数。

你需要做的第一件事是使你的函数更加复杂。下一个例子将从jsonResults变量中获取值,并使用内置的map函数遍历它。

map函数将使您能够按顺序遍历数组中的每一项,并评估每一个值。它还会告诉你该项目的当前索引。它还会在每次迭代中返回整个数组。

让我们来看看这个。如果您下载了这本书的代码,请打开清单 8-7。否则,遵循图 8-9

img/313453_3_En_8_Fig9_HTML.jpg

图 8-9

单击调试面板中的“单步执行”按钮,可以将您从映射函数移动到 console.log 函数

在遍历函数体时,使用console.log方法输出已经返回的值。您还可以使用这个方法来输出一个名为checkIndex的函数的结果。

checkIndex函数查看当前索引,看它是否能被 2 整除,并返回 true 或 false 值。您将使用这个函数来完成调试工具。

如前所述,第一个按钮恢复脚本,让它不间断地运行。“下一步”按钮允许您单步执行下一个函数调用。

如果打开了源代码面板,请在第 10 行设置一个断点。这将停止应用,您可以开始看到这到底意味着什么。

当应用正在执行时,如果你点击按钮,所有的东西都应该停在第 10 行。如果您单击 Step Over 按钮,它会将您带到函数中的下一行,在这里您开始使用map方法遍历数组中的所有项。如果你再次点击它,它将跳过map功能,进入列表中的下一个功能,即console.log功能。

如果您再次单击“跳过”按钮,指针将回到第 10 行。在下一个示例中,您将使用“单步执行”按钮来检查函数内部发生了什么。

如果通过刷新页面然后单击按钮来重新开始,调试器将再次停止在第 10 行。

现在,如果您单击“单步执行”按钮,您仍然会移动到下一个函数,在这里您将开始遍历数组中的所有项。

如果您第二次点击该按钮,而不是移动到下一个函数,您现在将进入当前函数的详细信息,并开始查看在map函数中正在处理的值。参见图 8-10

img/313453_3_En_8_Fig10_HTML.jpg

图 8-10

单击“调试”面板中的“单步执行”按钮可以进入映射功能

继续使用该按钮时,向下移动功能中的项目列表,直到到达下一个console.log功能。此时使用“步入”按钮会将您带到checkIndex功能。

到目前为止,您已经逐步完成了saveData功能中的每一项。然而,最后一行执行了一个新的函数,让您可以逐步执行。

您现在开始使用相同的工具逐行遍历checkIndex函数,检查这个函数是如何执行的。

列表中的下一个按钮与“步入”按钮的作用相反。它跳出当前函数。

在这个例子中,如果你正在单步执行checkIndex函数并选择退出,你将被带回到最初调用checkIndex函数的saveData函数。

列表中的下一个按钮是“步进”按钮。这个按钮可以让你一行一行地执行你的函数,并执行所有编写的东西。

最后两个按钮允许您停用或重新激活应用中的所有断点,并在出现异常时暂停。JavaScript 中的异常发生在错误发生的时候。

摘要

本章探讨了一些可以用来调试网站的工具。这里的重点是 JavaScript,但是您也可以检查和编辑 CSS 和 HTML,并且您可以模拟不同的设备来测试您的站点的响应能力。

其他工具包括网络监视器,用于查看代码从服务器发出需要多长时间,或者发出请求和获得响应需要多长时间。“性能”面板让您看到代码执行的速度,而“内存”面板确保您的代码以有效的方式使用浏览器的内存。

所有这些工具都在客户端。您一直在使用浏览器来帮助您理解 JavaScript 如何工作以及如何调试它。

随着项目变得越来越复杂,您可能会开始添加库或框架来帮助组织应用并添加您需要的功能。下一章将介绍如何作为客户端开发人员利用 NodeJS。