初探 JavaScript

现在你已经学习了一些关于 JavaScript 的理论知识以及它的用途,我们将通过一个实际操作教程,引导你了解创建简单 JavaScript 程序的过程。在这里,你将一步步构建一个简单的“猜数字”游戏。

预备知识 了解 HTMLCSS 基础知识
学习成果
  • 像程序员一样思考。
  • 体验编写 JavaScript 的感受。

我们想在这里设定一个非常明确的期望:你不会期望在这篇文章结束时学会 JavaScript,甚至不会理解我们要求你编写的所有代码。相反,我们希望让你了解 JavaScript 的功能如何协同工作,以及编写 JavaScript 的感觉。在后续的文章中,你将更详细地回顾这里展示的所有功能,所以如果你不能立即理解所有内容,请不要担心!

注意:你将在 JavaScript 中看到许多代码功能与其他编程语言(如函数、循环等)相同。代码语法看起来不同,但概念在很大程度上仍然相同。

介绍我们的“猜数字游戏”示例

在本文中,我们将向你展示如何构建以下游戏

尝试玩一下——在继续之前熟悉游戏。

像程序员一样思考

编程中最难学的事情之一不是你需要学习的语法,而是如何应用它来解决现实世界中的问题。你需要开始像程序员一样思考——这通常涉及到查看程序需要做什么的描述,找出实现这些事情需要哪些代码功能,以及如何使它们协同工作。

这需要努力工作、编程语法经验和练习——再加上一点创造力。你编写的代码越多,就会越擅长。我们不能保证你会在五分钟内培养出“程序员大脑”,但我们将为你提供大量机会,让你在这里以及整个课程中练习像程序员一样思考。

初步设计概要

让我们想象一下你的老板给你提供了以下创建这个游戏的简要说明

我想让你创建一个简单的“猜数字”类型的游戏。它应该在 1 到 100 之间选择一个随机数字,然后挑战玩家在 10 轮内猜出这个数字。每轮之后,应该告诉玩家他们是对还是错,如果错了,是猜得太低还是太高。它还应该告诉玩家他们之前猜过的数字。游戏将在玩家猜对或用完回合后结束。游戏结束时,玩家应该可以选择重新开始游戏。

看了这份简报后,我们首先要做的是,尽可能以程序员的思维方式,将其分解为简单的可操作任务

  1. 生成一个介于 1 到 100 之间的随机数。

  2. 记录玩家所处的轮数。从 1 开始。

  3. 为玩家提供一种猜测数字的方法。

  4. 一旦提交猜测,首先将其记录下来,以便用户可以看到他们之前的猜测。

  5. 接下来,检查它是否是正确的数字。

  6. 如果正确

    1. 显示祝贺消息。
    2. 阻止玩家输入更多猜测(这会搞乱游戏)。
    3. 显示允许玩家重新开始游戏的控件。
  7. 如果错误且玩家还有回合数

    1. 告诉玩家他们错了,以及他们的猜测是太高还是太低。
    2. 允许他们输入另一个猜测。
    3. 将回合数增加 1。
  8. 如果错误且玩家没有回合数

    1. 告诉玩家游戏结束。
    2. 阻止玩家输入更多猜测(这会搞乱游戏)。
    3. 显示允许玩家重新开始游戏的控件。
  9. 游戏重新开始后,确保游戏逻辑和 UI 完全重置,然后返回步骤 1。

现在,让我们继续,看看如何将这些步骤转化为代码,构建示例,并在过程中探索 JavaScript 功能。

初始设置

要开始本教程,我们希望你在代码编辑器中使用新的 HTML 文件制作以下代码的本地副本。

html
<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />

    <title>Number guessing game</title>

    <style>
      html {
        font-family: sans-serif;
      }

      body {
        width: 50%;
        max-width: 800px;
        min-width: 480px;
        margin: 0 auto;
      }

      .form input[type="number"] {
        width: 200px;
      }

      .lastResult {
        color: white;
        padding: 3px;
      }
    </style>
  </head>

  <body>
    <h1>Number guessing game</h1>

    <p>
      We have selected a random number between 1 and 100. See if you can guess
      it in 10 turns or fewer. We'll tell you if your guess was too high or too
      low.
    </p>

    <div class="form">
      <label for="guessField">Enter a guess: </label>
      <input
        type="number"
        min="1"
        max="100"
        required
        id="guessField"
        class="guessField" />
      <input type="submit" value="Submit guess" class="guessSubmit" />
    </div>

    <div class="resultParas">
      <p class="guesses"></p>
      <p class="lastResult"></p>
      <p class="lowOrHi"></p>
    </div>

    <script>
      // Your JavaScript goes here
    </script>
  </body>
</html>

将其保持在文本编辑器中打开,并在网络浏览器中也打开它。目前你会看到一个简单的标题、一段说明和一个用于输入猜测的表单,但该表单目前不会执行任何操作。

你将把所有 JavaScript 代码添加到 HTML 底部的 <script> 元素中。

html
<script>
  // Your JavaScript goes here
</script>

添加变量来存储我们的数据

让我们开始吧。首先,在你的 <script> 元素中添加以下几行

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

const guesses = document.querySelector(".guesses");
const lastResult = document.querySelector(".lastResult");
const lowOrHi = document.querySelector(".lowOrHi");

const guessSubmit = document.querySelector(".guessSubmit");
const guessField = document.querySelector(".guessField");

let guessCount = 1;
let resetButton;

此代码段设置了我们需要存储程序将使用的数据的变量(和常量)。

变量本质上是值的名称(例如数字或文本字符串)。你使用关键字 let 后跟变量名称来创建变量。

常量也用于命名值,但与变量不同,一旦设置,你就无法更改其值。在这种情况下,我们使用常量来存储对用户界面部分的引用。其中一些元素中的文本可能会更改,但每个常量始终引用初始化时相同的 HTML 元素。你使用关键字 const 后跟常量名称来创建常量。

你可以使用等号 (=) 后跟要赋给变量或常量的值来为其赋值。

在我们的例子中

  • 第一个变量 randomNumber 被赋予一个介于 1 和 100 之间的随机数,该数字通过数学算法计算得出。

  • 前三个常量均用于存储对 HTML 中结果段落的引用,并用于稍后在代码中将值插入段落(请注意它们如何位于一个 <div> 元素中,该元素本身稍后在重新开始游戏时用于选择所有三个段落进行重置)

    html
    <div class="resultParas">
      <p class="guesses"></p>
      <p class="lastResult"></p>
      <p class="lowOrHi"></p>
    </div>
    
  • 接下来的两个常量存储对表单文本输入和提交按钮的引用,并用于稍后处理猜测的提交。

    html
    <label for="guessField">Enter a guess: </label>
    <input type="number" id="guessField" class="guessField" />
    <input type="submit" value="Submit guess" class="guessSubmit" />
    
  • 我们最后的两个变量存储一个猜测计数为 1(用于跟踪玩家已进行多少次猜测),以及对尚不存在(但稍后会出现)的重置按钮的引用。

函数

接下来,在你的上一个 JavaScript 代码下方添加以下内容

js
function checkGuess() {
  console.log("I am a placeholder");
}

函数是可重用的代码块,你可以编写一次并反复运行,从而避免一遍又一遍地重复代码。定义函数有几种方法,但现在我们将专注于一种简单的类型。在这里,我们使用关键字 function 后跟一个名称,并在其后加上括号来定义了一个函数。之后,我们放置了两个大括号 ({ })。在大括号内部是每当我们调用函数时要运行的所有代码。

当我们要运行代码时,我们输入函数名称后跟括号。

我们现在试试。保存你的代码并在浏览器中刷新页面。然后进入开发者工具 JavaScript 控制台,并输入以下行

js
checkGuess();

按下 Return/Enter 后,你应在控制台中看到 I am a placeholder;我们已在代码中定义了一个函数,每当我们调用它时,它都会输出一个占位符消息。

文本字符串

字符串用于表示文本。我们已经见过一个字符串变量:在以下代码中,"I am a placeholder" 是一个字符串

js
function checkGuess() {
  console.log("I am a placeholder");
}

你可以使用双引号(")或单引号(')声明字符串,但对于单个字符串声明的开头和结尾,你必须使用相同的形式:你不能写 "I am a placeholder'

您还可以使用反引号(`)声明字符串。以这种方式声明的字符串称为*模板字面量*,并具有一些特殊属性。特别是,您可以在其中嵌入其他变量甚至表达式。

js
const name = "Mahalia";

const greeting = `Hello ${name}`;

这为你提供了一种将字符串连接起来的机制。

条件请求

条件代码块允许你根据特定条件是否为真有选择地运行代码。它们有点像函数,但又不同。让我们通过向示例添加内容来探索条件。

我想可以肯定地说,我们不希望 checkGuess() 函数仅仅吐出一个占位符消息。我们希望它检查玩家的猜测是否正确,并做出适当的响应。

此时,用以下版本替换你当前的 checkGuess() 函数

js
function checkGuess() {
  const userGuess = Number(guessField.value);
  if (guessCount === 1) {
    guesses.textContent = "Previous guesses:";
  }
  guesses.textContent = `${guesses.textContent} ${userGuess}`;

  if (userGuess === randomNumber) {
    lastResult.textContent = "Congratulations! You got it right!";
    lastResult.style.backgroundColor = "green";
    lowOrHi.textContent = "";
    setGameOver();
  } else if (guessCount === 10) {
    lastResult.textContent = "!!!GAME OVER!!!";
    lowOrHi.textContent = "";
    setGameOver();
  } else {
    lastResult.textContent = "Wrong!";
    lastResult.style.backgroundColor = "red";
    if (userGuess < randomNumber) {
      lowOrHi.textContent = "Last guess was too low!";
    } else if (userGuess > randomNumber) {
      lowOrHi.textContent = "Last guess was too high!";
    }
  }

  guessCount++;
  guessField.value = "";
  guessField.focus();
}

好长的代码——呼!让我们逐一讲解每个部分的作用。

  • 第一行声明了一个名为 userGuess 的常量,并将其值设置为文本字段中输入的当前值。我们还将此值通过内置的 Number() 构造函数运行,以确保该值确实是一个数字。

  • 接下来,我们遇到了第一个条件代码块。最简单的条件块以关键字 if 开头,然后是一些括号,然后是一些大括号。在括号内部,我们包含一个测试。如果测试返回 true,我们运行大括号内部的代码。如果不是,我们不运行,并继续下一个代码段。在这种情况下,我们测试 guessCount 变量是否等于 1(也就是说,这是否是玩家的第一次尝试)

    js
    guessCount === 1;
    

    如果是,我们将猜测段落的文本内容设置为 Previous guesses:。如果不是,则不设置。

  • 接下来,我们使用模板字面量将当前的 userGuess 值附加到 guesses 段落的末尾,中间留一个空格。

  • 下一个代码块进行了一些检查

    • 第一个 if (){ } 检查用户的猜测是否等于我们 JavaScript 顶部设置的 randomNumber。如果是,玩家猜对了,游戏获胜,所以我们用漂亮的绿色显示祝贺消息,清除低/高猜测信息框的内容,并运行一个名为 setGameOver() 的函数,我们稍后将讨论该函数。
    • 现在我们使用 else if (){ } 结构在上次测试的末尾又连接了一个测试。这个测试检查这轮是否是用户的最后一轮。如果是,程序会执行与上一个块相同的操作,只是用游戏结束消息而不是祝贺消息。
    • 附加到此代码末尾的最终块(else { })包含仅在其他两个测试均不返回 true(玩家没有猜对,但他们还有更多的猜测机会)时运行的代码。在这种情况下,我们告诉他们他们错了,然后我们执行另一个条件测试,检查猜测是高于还是低于答案,并根据需要显示进一步的消息,告诉他们是高还是低。
  • 函数中的最后三行使我们为提交下一个猜测做好准备。我们将 guessCount 变量加 1,以便玩家用尽他们的回合(++ 是一个增量操作——增加 1),并清空表单文本字段中的值并再次聚焦它,为输入下一个猜测做好准备。

事件

此时,我们有一个实现得很好的 checkGuess() 函数,但它不会做任何事情,因为我们还没有调用它。理想情况下,我们希望在按下“提交猜测”按钮时调用它,为此我们需要使用事件。事件是浏览器中发生的事情——按钮被点击、页面加载、视频播放等——我们可以响应这些事件运行代码块。事件监听器观察特定事件并调用事件处理程序函数,这些函数在事件触发时运行。

在你的 checkGuess() 函数下方添加以下行

js
guessSubmit.addEventListener("click", checkGuess);

在这里,我们正在为 guessSubmit 按钮添加一个事件监听器。这是一个方法,它接受两个输入值(称为*参数*)——我们正在监听的事件类型(在本例中为字符串 click)以及当事件发生时我们想要运行的函数(在本例中为 checkGuess())。请注意,在 addEventListener() 中编写时,我们不需要指定括号。

现在尝试保存并刷新你的代码,你的示例应该可以工作——到某个程度。现在唯一的问题是,如果你猜对答案或用完猜测次数,游戏就会中断,因为我们尚未定义游戏结束时应该运行的 setGameOver() 函数。现在让我们添加缺失的代码并完成示例功能。

完成游戏功能

让我们将 setGameOver() 函数添加到代码的底部,然后逐步讲解它。现在,在其余 JavaScript 代码下方添加它

js
function setGameOver() {
  guessField.disabled = true;
  guessSubmit.disabled = true;
  resetButton = document.createElement("button");
  resetButton.textContent = "Start new game";
  document.body.append(resetButton);
  resetButton.addEventListener("click", resetGame);
}
  • 前两行通过将其 disabled 属性设置为 true 来禁用表单文本输入和按钮。这是必要的,因为如果我们不这样做,用户可能会在游戏结束后提交更多猜测,这会搞乱一切。
  • 接下来的三行生成一个新的 <button> 元素,将其文本标签设置为“开始新游戏”,并将其添加到现有 HTML 的底部。
  • 最后一行在新按钮上设置了一个事件监听器,以便在点击它时运行一个名为 resetGame() 的函数。

现在我们还需要定义 resetGame()!将以下代码(同样添加到你的 JavaScript 底部)

js
function resetGame() {
  guessCount = 1;

  const resetParas = document.querySelectorAll(".resultParas p");
  for (const resetPara of resetParas) {
    resetPara.textContent = "";
  }

  resetButton.parentNode.removeChild(resetButton);

  guessField.disabled = false;
  guessSubmit.disabled = false;
  guessField.value = "";
  guessField.focus();

  lastResult.style.backgroundColor = "white";

  randomNumber = Math.floor(Math.random() * 100) + 1;
}

这段相当长的代码块将所有内容完全重置为游戏开始时的状态,以便玩家可以再玩一次。

具体来说,它

  • guessCount 重置为 1。
  • 清空所有信息段落中的文本。我们选择 <div class="resultParas"></div> 内的所有段落,然后遍历每个段落,将其 textContent 设置为 ""(一个空字符串)。
  • 从我们的代码中删除重置按钮。
  • 重新启用表单元素,并清空并聚焦文本字段,为输入新猜测做好准备。
  • 移除 lastResult 段落的背景颜色。
  • 生成一个新的随机数,这样你就不会再猜相同的数字了!

至此,你应该拥有一个基本完全正常运行的游戏——恭喜!

在本文中,我们现在剩下要做的就是讨论你已经看到的一些其他重要的代码功能,尽管你可能没有意识到。

循环

上面,我们提到了循环,这是编程中一个非常重要的概念,它允许你重复运行一段代码,直到满足某个条件。

让我们通过一个基本示例向你展示这意味着什么。再次前往你的浏览器开发者工具 JavaScript 控制台,将以下代码粘贴进去,然后按 Enter/Return

js
const fruits = ["apples", "bananas", "cherries"];
for (const fruit of fruits) {
  console.log(fruit);
}

发生了什么?字符串 'apples', 'bananas', 'cherries' 在你的控制台打印出来了。

这是因为循环。行 const fruits = ['apples', 'bananas', 'cherries']; 创建了一个数组,它是一组值(在本例中为字符串)。

然后我们使用 for...of 循环来获取数组中的每个项并对其运行一些 JavaScript。行 for (const fruit of fruits) 表示

  1. 获取 fruits 中的第一个值并将其存储在一个名为 fruit 的变量中。
  2. 运行 {} 大括号之间的代码(在这种情况下,将 fruit 值记录到控制台)。
  3. 将下一个数组值存储在 fruit 中,并重复步骤 2,直到到达 fruits 数组的末尾。

现在我们来看看我们猜数字游戏中的循环——以下代码可以在 resetGame() 函数中找到

js
const resetParas = document.querySelectorAll(".resultParas p");
for (const resetPara of resetParas) {
  resetPara.textContent = "";
}

此代码使用 querySelectorAll() 方法创建一个包含 <div class="resultParas"> 内所有段落的变量,然后遍历每个段落,删除其文本内容。

请注意,尽管 resetPara 是一个常量,但我们可以更改其内部属性,例如 textContent

总结

至此,示例的构建就完成了。你已经到达了终点——干得好!尝试你的最终代码,或者在这里玩我们的完成版本。如果你的示例版本无法运行,请对照源代码进行检查。

下一课也可能有所帮助——在其中,我们讨论了编写 JavaScript 代码时可能出现的问题,同时回顾了“猜数字”游戏。