开篇寄语在上一篇文章中我们动手构建了一个完整的“猜数字”游戏从需求分析到代码实现一步步体验了变量、函数、条件语句、事件和循环等核心概念的运用。但在实际编程过程中代码很少能一次性完美运行。你可能会遇到程序完全不工作的情况也可能遇到程序虽然能跑起来但结果却不符合预期的尴尬。这完全正常即使是经验丰富的开发者每天也在和各种代码错误打交道。今天这篇文章的目标就是帮助你建立独立发现和修复 JavaScript 代码问题的能力与信心。我们将系统学习错误的分类、阅读错误信息的方法、以及使用浏览器开发者工具进行调试的基本技巧。错误类型语法错误与逻辑错误在开始实际调试之前我们需要先建立一个重要的认知框架代码中的错误并不是千篇一律的它们大致可以分为两种类型理解这两种类型的区别对于高效排错至关重要。第一种是语法错误。语法错误指的是代码中存在拼写错误或不符合语言规则的写法这类错误会导致程序完全无法运行或者在运行到出错位置时突然中断。好消息是语法错误通常会触发浏览器的错误提示你会在开发者控制台中看到明确的出错信息告诉你错误发生在哪一行、是什么类型的错误。只要你能读懂这些错误信息并且对 JavaScript 的基本语法足够熟悉语法错误通常是比较容易定位和修复的。第二种是逻辑错误。逻辑错误更加隐蔽和棘手。这类错误的代码在语法上是完全正确的程序可以正常运行不会抛出任何错误提示但最终的运行结果却与你的预期不一致。换句话说程序忠实地执行了你写的代码但你写的代码并没有正确地表达你真正想要的逻辑。由于浏览器不会为逻辑错误提供任何提示你需要通过仔细分析代码的执行流程、借助调试工具来追踪变量的值才能定位问题所在。对于初学者来说逻辑错误往往比语法错误更难发现和修复。在你编程生涯的初级阶段把错误划分为语法错误和逻辑错误这两大类已经足够帮助你理清思路。随着经验的增长你还会遇到更多细分的情况但万变不离其宗核心的排查思路都是相通的。一个故意出错的示例为了让你能够在安全的环境中练习排错技巧我们特意准备了一个包含多个错误的猜数字游戏版本。与其直接在你自己的代码上盲目摸索不如先用这个有问题的版本来学习系统化的调试方法掌握之后再回头修复你自己的项目效率会高得多。请在你的文本编辑器和浏览器中分别打开这个有错误的游戏页面。先试着玩一下你会发现当你在输入框中输入一个数字并点击提交按钮时游戏完全没有任何反应。这就是我们遇到的第一个问题。面对这种情况很多初学者可能会感到无从下手不知道从哪里开始检查。其实答案很简单打开浏览器的开发者工具查看 JavaScript 控制台。浏览器已经在那里为你准备好了错误信息只是等待你去阅读和理解。修复语法错误第一轮排查在之前的课程中你已经学会了在浏览器的开发者工具中打开 JavaScript 控制台并在其中输入简单的命令来测试代码。现在我们将更进一步学习如何利用控制台提供的错误信息来定位和修复代码中的问题。当 JavaScript 代码被浏览器加载并执行时如果遇到了语法错误JavaScript 引擎会停止执行并在控制台中输出一条错误消息。这条消息包含了非常有价值的信息学会阅读它是每个开发者的基本功。打开有错误版本的页面然后打开 JavaScript 控制台你会看到一条错误信息其中包含几个关键的部分。从这些信息中浏览器告诉我们错误发生在第 86 行错误类型是“TypeError”具体描述是“guessSubmit.addeventListener is not a function”翻译过来就是“guessSubmit.addeventListener不是一个函数”。这条错误信息非常有用。它直接告诉了我们问题的本质我们在代码中试图调用一个叫做 addeventListener 的方法但浏览器认为这个方法并不存在。这时候你应该立刻警觉是不是把方法名拼错了让我们去代码编辑器中查看第 86 行guessSubmit.addeventListener(click,checkGuess);果然addeventListener 这个单词的拼写有误。正确的方法名应该是addEventListener注意其中 Event 的首字母 E 需要大写。这是一个典型的驼峰命名法JavaScript 中的内置方法和属性几乎都遵循这种命名规则。我们把错误的拼写改正过来guessSubmit.addEventListener(click,checkGuess);这个错误非常直观地说明了一个重要的原则JavaScript 是严格区分大小写的。addEventListener和addeventListener在 JavaScript 眼中是两个完全不同的名字前者是浏览器提供的合法方法后者则是一个未定义的标识符。任何细微的拼写差异包括大小写错误、漏字多字都会导致代码无法正常运行。如果你对某个方法名的准确拼写不确定最好的习惯是去查阅 MDN 文档或者在搜索引擎中搜索“MDN 你需要的特性名称”。修复语法错误第二轮排查保存修改后的文件并刷新页面你会发现刚才那条错误信息消失了。这是一个好迹象说明我们已经成功修复了第一个问题。但是别高兴得太早试着输入一个数字并点击提交按钮你会发现页面上又出现了另一条错误信息。这一次的错误信息显示在第 78 行内容为“TypeError: lowOrHi is null”。这里出现了一个新的概念null。在 JavaScript 中null 是一个特殊的值表示“什么都没有”或者“没有值”。当一个变量的值为 null 时说明这个变量已经被声明了但它的值是一个空值没有任何有意义的内容。当我们试图在一个值为 null 的变量上访问 textContent 属性时浏览器就会抛出类型错误因为你不能从一个空值上读取任何属性。让我们找到第 78 行查看代码lowOrHi.textContent你猜高了;这一行代码试图将 lowOrHi 变量的 textContent 属性设置为字符串“你猜高了”但操作失败了因为 lowOrHi 当前的值是 null它并没有指向任何有效的 HTML 元素。要找出 lowOrHi 为什么会是 null我们需要追溯这个变量最初是在哪里被赋值的。在代码中搜索 lowOrHi最早出现的地方在第 48 行constlowOrHidocument.querySelector(lowOrHi);这一行的意图是使用 document.querySelector 方法获取页面上某个元素的引用然后将其赋值给 lowOrHi 常量。问题出在 querySelector 方法的参数上。querySelector 接收一个 CSS 选择器字符串作为参数而这里传入的“lowOrHi”并不包含任何选择器符号。如果我们想要通过类名来选择元素选择器必须以一个点开头。正确的写法应该是“.lowOrHi”前面的点号表示这是一个类选择器。为了验证我们的推测可以在代码中插入一行临时的调试语句。在第 49 行添加以下代码console.log(lowOrHi);console.log 方法是 JavaScript 中最常用也最实用的调试工具之一。它可以将括号中的值输出到浏览器的控制台中让你直观地看到某个变量在特定时刻的值。保存并刷新页面查看控制台的输出你会看到 lowOrHi 的值确实是 null这证实了我们的判断。现在将第 48 行的选择器修改为正确的形式constlowOrHidocument.querySelector(.lowOrHi);再次保存并刷新控制台中 console.log 的输出应该变成了一个指向 p 元素的引用而不是 null。问题解决了。当你确认代码已经修复之后可以选择删除那行临时的 console.log也可以保留它在代码中作为参考这个选择权在你。修复语法错误第三轮排查经过前两轮的修复游戏已经能够正常运行大部分流程了。你现在可以输入数字进行猜测页面也会给出相应的反馈。但是当你猜对数字或者十次机会用完之后游戏会显示游戏结束然而如果你点击“开始新游戏”的按钮程序又会抛出错误。这次的错误信息与第一轮非常相似还是“TypeError: resetButton.addeventListener is not a function”只不过这次它发生在第 94 行。有了第一轮的经验你一眼就能看出问题所在我们又犯了完全相同的拼写错误。找到第 94 行resetButton.addeventListener(click,resetGame);将 addeventListener 改为 addEventListener 即可。这个小插曲再次印证了一个道理在编程中同样的错误很容易被重复犯下尤其是当你在复制粘贴代码时。一旦你掌握了一种错误的识别模式就应该对整个代码文件进行一次全面检查看看还有没有其他位置存在相同的问题。逻辑错误当程序运行但结果不对经过三轮语法错误的修复现在游戏从表面上看已经可以顺利运行了不会再抛出任何错误提示。但是如果你多玩几盘就会注意到一个奇怪的现象每次游戏生成的随机数字不是 0 就是 1而且似乎永远都是 1。这显然不是我们想要的我们的需求是生成 1 到 100 之间的随机数。这种情况就是典型的逻辑错误。代码在语法上没有毛病浏览器也不会给出任何错误提示但程序的行为却与预期严重不符。逻辑错误通常比语法错误更难排查因为你无法直接依赖浏览器的错误信息必须通过分析代码的逻辑来推断问题的根源。我们需要找到与随机数生成相关的代码。在游戏开始时设定随机数的语句大约在第 44 行letrandomNumberMath.floor(Math.random())1;在重新开始游戏时重新生成随机数的语句大约在第 113 行randomNumberMath.floor(Math.random())1;两行代码的逻辑完全一致因此它们会产生相同的错误结果。为了验证这两行代码到底生成了什么样的随机数我们再次请出 console.log 来帮忙。在两行代码之后各插入一条输出语句console.log(randomNumber);保存并刷新然后试玩几轮游戏同时观察控制台的输出。你会发现每次输出的 randomNumber 都是 1几乎没有例外。这证实了问题就出在这两行生成随机数的表达式上。修正逻辑错误分析表达式现在让我们来仔细分析Math.floor(Math.random()) 1这个表达式到底做了什么以及它为什么会总是返回 1。首先Math.random() 是 JavaScript 内置的方法它会生成一个 0 到 1 之间的随机十进制小数比如 0.5675493843、0.1234567890 等等。这个随机数可能非常接近 1但永远小于 1。接下来Math.floor() 方法接收一个数字作为参数它会舍弃该数字的小数部分返回小于或等于该数字的最大整数。也就是说它会对数字进行向下取整。由于 Math.random() 返回的值永远在 0 到 1 之间任何在这个区间内的数字经过向下取整之后结果一定是 0。这个 0 再加 1自然永远等于 1。这就是为什么我们的游戏生成的随机数始终是 1 的原因。正确的逻辑应该是怎样的呢我们需要生成 0 到 99 之间的随机整数然后加上 1从而得到 1 到 100 之间的结果。要得到 0 到 99 之间的随机整数我们需要先把 Math.random() 返回的 0 到 1 之间的小数乘以 100这样范围就变成了 0 到 100 之间的小数。然后再用 Math.floor 向下取整就能得到 0 到 99 之间的整数。最后加上 1范围就变成了 1 到 100。修正后的正确表达式为Math.floor(Math.random()*100)1;将两处随机数生成的代码都替换成这个正确的版本保存并刷新页面再玩几轮游戏你会发现随机数终于正常出现在 1 到 100 的范围之内了。这个案例很好地展示了逻辑错误的排查思路观察异常现象定位相关代码分析表达式的执行过程找出与预期不符的地方最后修正逻辑。其他常见错误速览除了本文前面详细讲解的几种错误之外JavaScript 开发中还经常遇到一些其他类型的错误。提前了解这些常见错误的特征和原因可以让你在日后的编程中更快地识别和解决问题。第一种常见错误是混淆了赋值运算符和比较运算符。在条件判断中应该使用三个等号表示严格等于比较但如果误写成了一个等号代码就变成了赋值操作。比如把if (userGuess randomNumber)误写成了if (userGuess randomNumber)。由于赋值表达式总是返回被赋予的值如果这个值可以被转换为 true条件就会永远成立导致游戏无论输入什么数字都显示猜对了。这种错误非常隐蔽因为它在语法上完全合法排查时需要仔细检查每一个条件判断中的运算符是否正确。第二种常见错误是函数调用时遗漏了结束括号。这种错误通常会触发“SyntaxError: missing ) after argument list”的提示意思是参数列表后面缺少了一个右括号。错误信息已经直接告诉了你问题的性质只需要仔细检查函数调用和条件语句中的括号是否成对出现即可。第三种常见错误与对象和函数的声明语法有关。如果你在定义函数时不小心在参数列表前面加了一个多余的花括号比如把 function checkGuess() 写成了 function checkGuess( {浏览器就会误解你的意图抛出“SyntaxError: missing : after property id”之类的错误。这说明编写代码时对括号和花括号的配对要格外留心。第四种是函数或条件结构中遗漏了闭合的花括号。这种错误会触发“SyntaxError: missing } after function body”的提示。如果代码的缩进层次比较混乱这类遗漏就很容易发生。保持规范的代码缩进习惯可以有效减少这类问题的出现。第五种错误与字符串有关。如果你漏写了字符串开头或结尾的引号浏览器会抛出类似“SyntaxError: expected expression, got string”或者“SyntaxError: string literal contains an unescaped line break”的错误。浏览器会尽力指出它在哪里遇到了意料之外的字符你只需要循着提示找到对应位置补上缺失的引号即可。面对所有这些错误一个通用的排查思路是首先转到错误信息所指的那一行检查该行的语法是否正确。如果那一行看起来没有问题再检查相邻的几行因为错误的原因有时并不精确地出现在提示行号的位置上可能是在之前的某一行就已经埋下了隐患。保持耐心逐行排查是调试过程中最重要的心态。小结与建议通过今天的学习你已经掌握了在简单 JavaScript 程序中排查和修复错误的基础知识。我们了解到代码错误分为语法错误和逻辑错误两大类语法错误通常有明确的错误提示逻辑错误则需要通过分析代码的行为来定位。我们实践了使用浏览器开发者控制台读取错误信息的方法学会了如何根据错误提示的行号和类型找到问题代码也掌握了使用 console.log 输出变量值来辅助调试的技巧。我们还分析了几种常见的错误类型及其特征这些经验将帮助你在日后的编程中更快地识别问题。需要坦诚地告诉你的是调试代码并不总是一件轻松简单的事情。有些错误可能隐藏得很深排查起来需要花费不少时间和精力。但掌握了本文所介绍的基本方法和思路至少可以为你节省大量的无效摸索时间让你能够更有条理地面对问题。当你遇到自己实在解决不了的错误时不要独自苦恼可以向社区或同行寻求帮助。在提问时记得清晰地描述你遇到的错误是什么、错误信息是什么、以及相关的那段代码是怎样的这些信息能帮助别人更快地理解你的问题并给出有效的建议。最后请记住一个重要的心态转变错误不是敌人而是学习的机会。每一次调试的经历都在加深你对这门语言运行机制的理解。一个从来没有遇到过错误的程序员很可能也从来没有真正写过什么有用的程序。所以当错误出现时不要沮丧把它当作一个待解的谜题冷静分析逐步排查你一定能够找到答案。在下一篇文章中我们将继续深入 JavaScript 的世界探索更多有趣的知识点。