什么地方出了错?JavaScript 疑难解答

当你在上一篇文章中构建 "猜数字" 游戏时,你可能会发现它不起作用。别担心 - 本文旨在为你提供一些有关如何查找和修复 JavaScript 程序中的错误的提示,从而使你免于为此类问题而烦恼。

¥When you built up the "Guess the number" game in the previous article, you may have found that it didn't work. Never fear — this article aims to save you from tearing your hair out over such problems by providing you with some tips on how to find and fix errors in JavaScript programs.

先决条件: 对 HTML 和 CSS 有基本的了解,对 JavaScript 有了解。
目标: 获得开始修复自己代码中的问题的能力和信心。

错误类型

¥Types of error

一般来说,当你在代码中出现错误时,你会遇到两种主要类型的错误:

¥Generally speaking, when you do something wrong in code, there are two main types of error that you'll come across:

  • 语法错误:这些是代码中的拼写错误,实际上会导致程序根本无法运行,或者中途停止工作 - 通常也会向你提供一些错误消息。只要你熟悉正确的工具并知道错误消息的含义,这些通常就可以修复!
  • 逻辑错误:这些错误的语法实际上是正确的,但代码不是你想要的,这意味着程序成功运行但给出了错误的结果。这些错误通常比语法错误更难修复,因为通常没有错误消息来引导你找到错误的根源。

好吧,事情没那么简单 - 当你深入研究时,还有一些其他的差异化因素。但上述分类适用于你职业生涯的早期阶段。我们将继续研究这两种类型。

¥Okay, so it's not quite that simple — there are some other differentiators as you drill down deeper. But the above classifications will do at this early stage in your career. We'll look at both of these types going forward.

一个错误的例子

¥An erroneous example

首先,让我们回到我们的猜数字游戏 - 只不过这次我们将探索一个故意引入一些错误的版本。转到 GitHub 并为自己制作 number-game-errors.html 的本地副本(参见 在这里实时运行)。

¥To get started, let's return to our number guessing game — except this time we'll be exploring a version that has some deliberate errors introduced. Go to GitHub and make yourself a local copy of number-game-errors.html (see it running live here).

  1. 首先,请在你喜欢的文本编辑器和浏览器中打开本地副本。
  2. 尝试玩游戏 - 你会发现当你按 "提交猜测" 按钮时,它不起作用!

注意:你可能有自己的游戏示例版本无法运行,你可能想要修复它!我们仍然希望你使用我们的版本来完成本文,以便你可以学习我们在这里教授的技术。然后你可以返回并尝试修复你的示例。

¥Note: You might well have your own version of the game example that doesn't work, which you might want to fix! We'd still like you to work through the article with our version, so that you can learn the techniques we are teaching here. Then you can go back and try to fix your example.

此时,让我们查阅开发者控制台,看看它是否报告任何语法错误,然后尝试修复它们。你将在下面了解如何操作。

¥At this point, let's consult the developer console to see if it reports any syntax errors, then try to fix them. You'll learn how below.

修复语法错误

¥Fixing syntax errors

在课程的前面,我们让你在 开发者工具 JavaScript 控制台 中输入一些简单的 JavaScript 命令(如果你不记得如何在浏览器中打开它,请按照前面的链接了解如何操作)。更有用的是,只要输入浏览器 JavaScript 引擎的 JavaScript 内存在语法错误,控制台就会向你提供错误消息。现在我们去打猎吧。

¥Earlier on in the course we got you to type some simple JavaScript commands into the developer tools JavaScript console (if you can't remember how to open this in your browser, follow the previous link to find out how). What's even more useful is that the console gives you error messages whenever a syntax error exists inside the JavaScript being fed into the browser's JavaScript engine. Now let's go hunting.

  1. 转到打开 number-game-errors.html 的选项卡,然后打开 JavaScript 控制台。你应该看到如下错误消息:

"Number guessing game" demo page in Firefox. One error is visible in the JavaScript console: "X TypeError: guessSubmit.addeventListener is not a function [Learn More] (number-game-errors.html:86:3)".

  1. 错误消息的第一行是:
    Uncaught TypeError: guessSubmit.addeventListener is not a function
    number-game-errors.html:86:15
    
    • 第一部分,Uncaught TypeError: guessSubmit.addeventListener is not a function,告诉我们出了什么问题。
    • 第二部分 number-game-errors.html:86:15 告诉我们错误来自代码中的哪个位置:文件 "number-game-errors.html" 的第 86 行,第 15 个字符。
  2. 如果我们在代码编辑器中查看第 86 行,我们会发现这一行:

    警告:错误消息可能不在第 86 行。

    ¥Warning: Error message may not be on line 86.

    如果你使用任何具有在本地计算机上启动实时服务器的扩展的代码编辑器,这将导致注入额外的代码。因此,开发者工具会将错误列为发生在非 86 行上。

    ¥If you are using any code editor with an extension that launches a live server on your local machine, this will cause extra code to be injected. Because of this, the developer tools will list the error as occurring on a line that is not 86.

    js
    guessSubmit.addeventListener("click", checkGuess);
    
  3. 错误消息显示 "guessSubmit.addeventListener 不是一个函数",这意味着 JavaScript 解释器无法识别我们正在调用的函数。通常,此错误消息实际上意味着我们拼写错误。如果你不确定某个语法的拼写是否正确,最好在 MDN 上查找该功能。目前最好的方法是使用你最喜欢的搜索引擎搜索 "mdn 功能名称"。在这种情况下,有一个可以节省你时间的快捷方式:addEventListener()
  4. 因此,查看此页面,错误似乎是我们将函数名称拼写错误!请记住,JavaScript 区分大小写,因此拼写或大小写上的任何细微差别都会导致错误。将 addeventListener 更改为 addEventListener 应该可以解决此问题。现在就这样做。

注意:有关此错误的更多详细信息,请参阅我们的 类型错误:"x" 不是函数 参考页。

¥Note: See our TypeError: "x" is not a function reference page for more details about this error.

第二轮语法错误

¥Syntax errors round two

  1. 保存页面并刷新,你应该会看到错误已经消失。
  2. 现在,如果你尝试输入猜测并按“提交猜测”按钮,你将看到另一个错误!

Screenshot of the same "Number guessing game" demo. This time, a different error is visible in the console, reading "X TypeError: lowOrHi is null".

  1. 这次报的错误是:
    Uncaught TypeError: can't access property "textContent", lowOrHi is null
    
    根据你使用的浏览器,你可能会在此处看到不同的消息。上面的消息是 Firefox 将向你显示的内容,但 Chrome 会向你显示以下内容:
    Uncaught TypeError: Cannot set properties of null (setting 'textContent')
    
    这是相同的错误,但不同的浏览器以不同的方式描述它。

    注意:页面加载后并没有立即出现此错误,因为此错误发生在函数内部(checkGuess() { } 块内)。正如你将在后面的 功能文章 中更详细地了解到的那样,函数内部的代码与函数外部的代码运行在不同的作用域中。在这种情况下,直到第 86 行运行 checkGuess() 函数后,代码才运行,并且不会引发错误。

    ¥Note: This error didn't come up as soon as the page was loaded because this error occurred inside a function (inside the checkGuess() { } block). As you'll learn in more detail in our later functions article, code inside functions runs in a separate scope than code outside functions. In this case, the code was not run and the error was not thrown until the checkGuess() function was run by line 86.

  2. 错误中给出的行号是 80。查看第 80 行,你将看到以下代码:
    js
    lowOrHi.textContent = "Last guess was too high!";
    
  3. 此行尝试将 lowOrHi 变量的 textContent 属性设置为文本字符串,但它不起作用,因为 lowOrHi 不包含它应该包含的内容。让我们看看这是为什么 - 尝试在代码中搜索 lowOrHi 的其他实例。你会发现最早的实例位于第 49 行:
    js
    const lowOrHi = document.querySelector("lowOrHi");
    
  4. 此时,我们尝试使变量包含对文档 HTML 中元素的引用。让我们看看该行运行后变量包含什么。在第 50 行添加以下代码:
    js
    console.log(lowOrHi);
    
    当我们尝试在第 49 行设置 lowOrHi 的值后,此代码会将其打印到控制台。请参阅 console.log() 了解更多信息。
  5. 保存并刷新,你现在应该在控制台中看到 console.log() 结果。

Screenshot of the same demo. One log statement is visible in the console, reading simply "null".

果然,此时 lowOrHi 的值为 null,这与 Firefox 错误消息 lowOrHi is null 相符。所以 49 行肯定有问题。null 值表示 "nothing" 或 "没有价值"。所以我们将 lowOrHi 设置为元素的代码出错了。

¥Sure enough, lowOrHi's value is null at this point, and this matches up with the Firefox error message lowOrHi is null. So there is definitely a problem with line 49. The null value means "nothing", or "no value". So our code to set lowOrHi to an element is going wrong.

  1. 让我们想想问题可能出在哪里。第 49 行使用 document.querySelector() 方法通过 CSS 选择器选择元素来获取对元素的引用。进一步查看我们的文件,我们可以找到有问题的段落:
    html
    <p class="lowOrHi"></p>
    
  2. 所以我们这里需要一个类选择器,它以点(.)开头,但是第 49 行传递到 querySelector() 方法的选择器没有点。这可能是问题所在!尝试将第 49 行中的 lowOrHi 更改为 .lowOrHi
  3. 再次尝试保存并刷新,你的 console.log() 语句应该返回我们想要的 <p> 元素。唷!另一个错误已修复!你现在可以删除 console.log() 行,也可以保留它以供日后参考 - 你可以选择。

注意:有关此错误的更多详细信息,请参阅我们的 类型错误:"x" 是(不是)"y" 参考页。

¥Note: See our TypeError: "x" is (not) "y" reference page for more details about this error.

第三轮语法错误

¥Syntax errors round three

  1. 现在,如果你尝试再次玩这个游戏,你应该会获得更多的成功 - 游戏应该玩得绝对顺利,直到你结束游戏,要么猜对了数字,要么用完猜测。
  2. 此时,游戏再次失败,并抛出与我们开始时相同的错误 - “TypeError:resetButton.addeventListener 不是一个函数”!然而,这次它被列为来自第 94 行。
  3. 看看第 94 行,很容易看出我们在这里犯了同样的错误。我们再次只需要将 addeventListener 更改为 addEventListener。现在就这样做。

逻辑错误

¥A logic error

此时,游戏应该可以正常运行,但是玩过几次后,你无疑会注意到游戏总是选择 1 作为你必须猜测的 "random" 数字。绝对不是我们想要的游戏玩法!

¥At this point, the game should play through fine, however after playing through a few times you'll undoubtedly notice that the game always chooses 1 as the "random" number you've got to guess. Definitely not quite how we want the game to play out!

游戏逻辑肯定有问题 - 游戏没有返回错误;它只是播放不正确。

¥There's definitely a problem in the game logic somewhere — the game is not returning an error; it just isn't playing right.

  1. 搜索 randomNumber 变量以及首次设置随机数的行。存储我们想要在游戏开始时猜测的随机数的实例应该在第 45 行左右:
    js
    let randomNumber = Math.floor(Math.random()) + 1;
    
  2. 在接下来的每场比赛之前生成随机数的代码位于第 113 行左右:
    js
    randomNumber = Math.floor(Math.random()) + 1;
    
  3. 为了检查这些行是否确实是问题所在,让我们再次求助于我们的朋友 console.log() — 在上述两行的正下方插入以下行:
    js
    console.log(randomNumber);
    
  4. 保存并刷新,然后玩几场游戏 - 你将看到 randomNumber 在记录到控制台的每个点上都等于 1。

通过逻辑进行工作

¥Working through the logic

为了解决这个问题,让我们考虑一下这条线是如何工作的。首先,我们调用 Math.random(),它生成一个 0 到 1 之间的随机十进制数,例如 0.5675493843。

¥To fix this, let's consider how this line is working. First, we invoke Math.random(), which generates a random decimal number between 0 and 1, e.g. 0.5675493843.

js
Math.random();

接下来,我们将调用 Math.random() 的结果传递到 Math.floor(),这会将传递给它的数字向下舍入到最接近的整数。然后我们将结果加 1:

¥Next, we pass the result of invoking Math.random() through Math.floor(), which rounds the number passed to it down to the nearest whole number. We then add 1 to that result:

js
Math.floor(Math.random()) + 1;

将 0 到 1 之间的随机小数向下舍入将始终返回 0,因此加 1 将始终返回 1。在向下舍入之前,我们需要将随机数乘以 100。下面的代码会给我们一个 0 到 99 之间的随机数:

¥Rounding a random decimal number between 0 and 1 down will always return 0, so adding 1 to it will always return 1. We need to multiply the random number by 100 before we round it down. The following would give us a random number between 0 and 99:

js
Math.floor(Math.random() * 100);

因此我们想加 1,得到一个 1 到 100 之间的随机数:

¥Hence us wanting to add 1, to give us a random number between 1 and 100:

js
Math.floor(Math.random() * 100) + 1;

尝试像这样更新两行,然后保存并刷新 - 游戏现在应该像我们想要的那样玩!

¥Try updating both lines like this, then save and refresh — the game should now play like we are intending it to!

其他常见错误

¥Other common errors

你在代码中还会遇到其他常见错误。本节重点介绍其中的大部分内容。

¥There are other common errors you'll come across in your code. This section highlights most of them.

语法错误:丢失的 ; 声明之前

¥SyntaxError: missing ; before statement

此错误通常意味着你在某一行代码末尾遗漏了分号,但有时它可能更神秘。例如,如果我们在 checkGuess() 函数中更改这一行:

¥This error generally means that you have missed a semicolon at the end of one of your lines of code, but it can sometimes be more cryptic. For example, if we change this line inside the checkGuess() function:

js
const userGuess = Number(guessField.value);

to

js
const userGuess === Number(guessField.value);

它抛出此错误是因为它认为你正在尝试做不同的事情。你应该确保不要混淆赋值运算符 (=)(将变量设置为等于某个值)与严格相等运算符 (===)(测试一个值是否等于另一个值并返回) true/false 结果。

¥It throws this error because it thinks you are trying to do something different. You should make sure that you don't mix up the assignment operator (=) — which sets a variable to be equal to a value — with the strict equality operator (===), which tests whether one value is equal to another, and returns a true/false result.

注意:有关此错误的更多详细信息,请参阅我们的 语法错误:丢失的 ; 声明之前 参考页。

¥Note: See our SyntaxError: missing ; before statement reference page for more details about this error.

无论你输入的猜测如何,程序总是说你赢了

¥The program always says you've won, regardless of the guess you enter

这可能是混淆赋值运算符和严格相等运算符的另一个症状。例如,如果我们要更改 checkGuess() 内的这一行:

¥This could be another symptom of mixing up the assignment and strict equality operators. For example, if we were to change this line inside checkGuess():

js
if (userGuess === randomNumber) {

to

js
if (userGuess = randomNumber) {

测试将始终返回 true,导致程序报告游戏已获胜。当心!

¥the test would always return true, causing the program to report that the game has been won. Be careful!

语法错误:参数列表后缺少 )

¥SyntaxError: missing ) after argument list

这个非常简单 - 它通常意味着你遗漏了函数/方法调用末尾的右括号。

¥This one is pretty simple — it generally means that you've missed the closing parenthesis at the end of a function/method call.

注意:有关此错误的更多详细信息,请参阅我们的 语法错误:参数列表后缺少 ) 参考页。

¥Note: See our SyntaxError: missing ) after argument list reference page for more details about this error.

语法错误:缺少:属性 id 之后

¥SyntaxError: missing : after property id

此错误通常与格式不正确的 JavaScript 对象有关,但在这种情况下,我们设法通过更改来获取它

¥This error usually relates to an incorrectly formed JavaScript object, but in this case we managed to get it by changing

js
function checkGuess() {

to

js
function checkGuess( {

这导致浏览器认为我们正在尝试将函数的内容作为参数传递到函数中。小心这些括号!

¥This has caused the browser to think that we are trying to pass the contents of the function into the function as an argument. Be careful with those parentheses!

语法错误:函数体后面缺少 }

¥SyntaxError: missing } after function body

这很简单 - 这通常意味着你遗漏了函数或条件结构中的一个大括号。我们通过删除 checkGuess() 函数底部附近的右大括号之一来得到此错误。

¥This is easy — it generally means that you've missed one of your curly braces from a function or conditional structure. We got this error by deleting one of the closing curly braces near the bottom of the checkGuess() function.

语法错误:预期的表达式,得到 'string' 或 SyntaxError:未终止的字符串文字

¥SyntaxError: expected expression, got 'string' or SyntaxError: unterminated string literal

这些错误通常意味着你遗漏了字符串值的左引号或右引号。在上面的第一个错误中,字符串将被浏览器发现的意外字符替换,而不是字符串开头的引号。第二个错误意味着字符串没有以引号结束。

¥These errors generally mean that you've left off a string value's opening or closing quote mark. In the first error above, string would be replaced with the unexpected character(s) that the browser found instead of a quote mark at the start of a string. The second error means that the string has not been ended with a quote mark.

对于所有这些错误,请考虑一下我们如何解决演练中看到的示例。当出现错误时,查看给定的行号,转到该行,看看是否可以发现问题所在。请记住,错误不一定会出现在该行上,而且错误可能不是由我们上面引用的完全相同的问题引起的!

¥For all of these errors, think about how we tackled the examples we looked at in the walkthrough. When an error arises, look at the line number you are given, go to that line and see if you can spot what's wrong. Bear in mind that the error is not necessarily going to be on that line, and also that the error might not be caused by the exact same problem we cited above!

注意:有关这些错误的更多详细信息,请参阅我们的 语法错误:意外的标记语法错误:未终止的字符串文字 参考页。

¥Note: See our SyntaxError: Unexpected token and SyntaxError: unterminated string literal reference pages for more details about these errors.

概括

¥Summary

现在我们已经掌握了找出简单 JavaScript 程序中错误的基础知识。找出代码中的问题并不总是那么简单,但至少这会节省你几个小时的睡眠,并让你在事情出现问题时进步得更快一些,尤其是在早期 你的学习旅程的各个阶段。

¥So there we have it, the basics of figuring out errors in simple JavaScript programs. It won't always be that simple to work out what's wrong in your code, but at least this will save you a few hours of sleep and allow you to progress a bit faster when things don't turn out right, especially in the earlier stages of your learning journey.

也可以看看

¥See also

  • 还有许多其他类型的错误,此处未列出;我们正在编写一份参考资料,详细解释它们的含义 - 请参阅 JavaScript 错误参考
  • 如果你在代码中遇到任何错误,并且在阅读本文后不确定如何修复,你可以寻求帮助!寻求有关 沟通渠道 的帮助。告诉我们你的错误是什么,我们将尽力帮助你。你的代码列表也会很有用。