现在,你已经学到了一些 JavaScript 的理论知识,以及用JavaScript 能够做些什么。下面我们会提供一个讲解 Javascript 基本特性的 Crash Course,课程是一个全面的实践性项目——循序渐进地构建一个简易版“猜数字”游戏。

预备知识: 计算机基础知识,初步理解HTML和CSS,了解JavaScript。
目标: 获得编写JavaScript的初体验,初步了解编写JavaScript时会涉及哪些内容。

我们并不要求你立刻完整理解所有代码,当前只是向你介绍一些抽象的概念,并且让你对JavaScript(以及其他编程语言)工作原理有一定的认知。所有具体特性将在后续文章中详细介绍。

注: 可以看到,JavaScript 中许多代码特性和其他编程语言是一致的( 函数、循环,等等)。尽管代码语法不尽相同,但概念基本类似。

像程序员一样思考

学习编程,语法本身并不难,真正困难的是如何应用它来解决现实世界的问题。 你要开始像程序员那样思考。一般讲,包括观察对程序所需功能的描述,为完成这些需求选定代码特性,以及如何使这些需求协同运行。

为达成这一点,努力编程,获取语法经验,注重实践,再加一点创造力,几项缺一不可。代码写的越多,就会完成的越优秀。虽然我们不能保证你在5分钟内拥有“程序员大脑”,但是整个课程中你将得到大量机会来训练程序员思维。

请牢记这一点,然后开始观察本文的示例,体会一下将其分解为可操作任务的大体过程。

示例——猜数字游戏

本文将向你演示如何构建下面的小游戏:

先玩上几盘,在继续之前先熟悉一下这个游戏。

假设你的老板给你布置了以下游戏设计任务要求:

我想让你开发一个猜数字游戏。游戏应随机选择一个 100 以内的自然数, 然后邀请玩家在 10 轮以内猜出这个数字。每轮后都应告知玩家的答案正确与否,如果出错了,则告诉他数字是低了还是高了。并且应显示出玩家前一轮所猜的数字。一旦玩家猜对,或者用尽所有机会,游戏将结束。游戏结束后,可以让玩家选择再次开始。

看到这个要求,首先我们要做的是将其分解成简单的可操作的任务,尽可能从程序员的思维去思考:

  1. 随机生成一个 100 以内的自然数。
  2. 记录玩家当前的轮数。从 1 开始。
  3. 为玩家提供一种猜测数字的方法。
  4. 一旦有结果提交,先将其记录下来,以便用户可以看到他们先前的猜测。
  5. 然后检查它是否正确。
  6. 如果正确:
    1. 显示祝贺消息。
    2. 阻止玩家继续猜测(这会使游戏混乱)。
    3. 显示控件允许玩家重新开始游戏。
  7. 如果出错,并且玩家有剩余轮次:
    1. 告诉玩家他们错了。
    2. 允许他们输入另一个猜测。
    3. 轮数加 1。
  8. 如果出错,并且玩家没有剩余轮次:
    1. 告诉玩家游戏结束。
    2. 阻止玩家继续猜测(这会使游戏混乱)。
    3. 显示控件允许玩家重新开始游戏。
  9. 一旦游戏重启,确保游戏的逻辑和UI完全重置,然后返回步骤1。

让我们继续,看看我们如何将这些步骤转换为代码,构建这个示例,从而探索 JavaScript 的功能。

初始设置

本教程开始前,请你将 number-guessing-game-start.html 文件保存下来  (在线查看)。同时在文本编辑器和Web浏览器中将其打开。此时,你将看到一个简单的标题,一段游戏说明,和一个用于输入猜测的表单,此时表单不会执行任何操作。

我们将HTML底部的 <script> 元素中添加新的代码

<script>

   // 开始编写 JavaScript 代码

</script>

添加变量以保存数据

让我们开始吧。首先,在 <script> 元素中添加以下代码:

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

var guesses = document.querySelector('.guesses');
var lastResult = document.querySelector('.lastResult');
var lowOrHi = document.querySelector('.lowOrHi');

var guessSubmit = document.querySelector('.guessSubmit');
var guessField = document.querySelector('.guessField');

var guessCount = 1;
var resetButton;

这段代码设置了存储数据的变量以供程序使用。变量本质上是值(例如数字或字符串)的容器。 你可以使用关键字 var 和一个名字来创建变量。然后,你可以使用等号(=)和一个值来为变量赋值。

在我们的示例中:

  • 我们用数学算法得出的1到100之间的随机数,并赋值给第一个变量(randomNumber)。
  • 接下来的三个变量均存储着一个引用,分别指向HTML结果段落中某个元素,用于在代码后面段落中插入值: 
  • <p class="guesses"></p>
    <p class="lastResult"></p>
    <p class="lowOrHi"></p>
  • 接下来的两个变量存储对表单文本输入和提交按钮的引用,并用于控制以后提交猜测:
    <label for="guessField">请猜数:</label>
    <input type="text" id="guessField" class="guessField">
    <input type="submit" value="确定" class="guessSubmit">
  • 倒数第二个变量存储一个计数器并初始化为 1(用于跟踪玩家猜测的次数),最后一个变量存储对重置按钮的引用,这个按钮尚不存在(但稍后就有了)。

: 在稍后的课程中,你将学到更多关于变量的信息。查看下一篇文章

函数(Function)

下面,在之前的代码中添加以下内容:

function checkGuess() {
  alert('我是一个占位符');
}
函数是可复用的代码块,可以一次编写,反复运行,从而节省了大量的重复代码。它们真的很有用。定义函数的方法很多,但现在我们先集中考虑当前这个简单的方式。 这里我们使用关键字 function 、一个函数名、一对小括号定义了一个函数。 随后是一对花括号({})。花括号内部是调用函数时要运行的所有代码。

要运行一个函数代码时,可以输入函数名加一对小括号。
 
让我们尝试一下。保存你的代码并刷新浏览器页面 。然后进入
开发者工具 JavaScript 控制台,并输入以下代码:
 
checkGuess();

在按下 Return/Enter 之后,你应该会看到一个告警窗口,显示 "我是一个占位符";我们在代码中定义了一个函数,当我们调用它时,函数创建了一个告警窗口

: 在后续课程中,你将学到更多有关函数的知识。

运算符(Operator)

JavaScript 运算符允许我们执行比较,做数学运算,连接字符串,以及其他类似的事情。

请保存代码以免丢失,然后刷新浏览器页面,打开 开发者工具 JavaScript 控制台。然后我们就可以尝试下文中的示例了:把下表中“示例”一列中的每一项都原封不动输入进来,每次输入完毕后都按下  Return/Enter ,可以看到返回的结果。如果你无法访问浏览器开发者工具,你随时都可以使用下面的简易版内置控制台。

首先让我们来看看算术运算符,例如:

运算符 名称 示例
+ 6 + 9
- 20 - 15
* 3 * 7
/ 10 / 5

您也可以使用+运算符将文本字符串连接在一起(编程中称为“串联”(concatenation))。 尝试依次输入以下行:

var name = 'Bingo';
name;
var hello = ' says hello!';
hello;
var greeting = name + hello;
greeting;

还有一些快捷操作符可用,称为复合赋值操作符。 例如,如果你只希望在现有字符串末尾添加一个新串,可以这样做:

name += ' says hello!';

这等价于:

name = name + ' says hello!';

在执行真/假比较时(例如在条件语句中,见下表),我们使用比较运算符,例如:

运算符 名称 示例
=== 严格等于(它们是否完全一样?) 5 === 2 + 4
!== 不等于(它们究竟哪里不一样?) 'Chris' !== 'Ch' + 'ris'
< 小于 10 < 6
> 大于 10 > 20

条件语句(Conditional)

回到我们的checkGuess()函数,我们希望它不仅能够给出一个占位符消息,同时还能检查玩家是否猜对,并做出适当的反应。我想这并不难吧。

现在,将当前的checkGuess()函数替换为此版本:

function checkGuess() {
  var userGuess = Number(guessField.value);
  if (guessCount === 1) {
    guesses.textContent = '上次猜的数:';
  }
  guesses.textContent += userGuess + ' ';
 
  if (userGuess === randomNumber) {
    lastResult.textContent = '恭喜你!猜对了';
    lastResult.style.backgroundColor = 'green';
    lowOrHi.textContent = '';
    setGameOver();
  } else if (guessCount === 10) {
    lastResult.textContent = '!!!GAME OVER!!!';
    setGameOver();
  } else {
    lastResult.textContent = '你猜错了!';
    lastResult.style.backgroundColor = 'red';
    if(userGuess < randomNumber) {
      lowOrHi.textContent = '你猜低了!';
    } else if(userGuess > randomNumber) {
      lowOrHi.textContent = '你猜高了';
    }
  }
 
  guessCount++;
  guessField.value = '';
  guessField.focus();
}

唷——好多的代码!让我们来逐段探究。

  • 第一行(行标 2)声明了一个名为userGuess的变量,并将其设置为在文本字段中输入的值。 我们还对这个值应用了内置的Number()方法,只是为了确保该值是一个数字。
  • 接下来,我们遇到我们的第一个条件代码块(3 - 5 行)。 条件代码块让你能够根据某个条件的真假来选择性地运行代码。 虽然看起来有点像一个函数,但它不是。 条件块的最简单形式是从关键字 if 开始,然后是一些括号,然后是一些花括号。 括号内包含一个比较。 如果比较结果为 true,就会执行花括号内的代码。 反之,花括号中的代码就会被跳过,从而执行下面的代码。 本文的示例中,比较测试的是 guessCount 变量是否等于1,即玩家是不是第一次猜数字:
  • guessCount === 1
    如果是, 我们让 guesses 段落的文本内容等于“上次猜的数:”。如果不是就不用了。
  • 第 6 行将当前 userGuess 值附加到 guesses 段落的末尾,并加上一个空格,以使每两个猜测值之间有一个空格。
  • 下一个代码块中(8 - 24 行)做了几个检查:
    • 第一个if(){ }检查用户的猜测是否等于在代码顶端设置的randomNumber 值。如果是,则玩家猜对了,游戏胜利,我们将向玩家显示一个漂亮的绿色的祝贺信息,并清除“高了 / 低了”信息框的内容,调用setGameOver()方法。
    • 紧接着是一个 else if(){ } 结构。它会检查这个回合是否是玩家的最后一个回合。如果是,程序将做与前一个程序块相同的事情,只是这次它显示的是 Game Over 而不是祝贺消息。
    • 最后的一个块是 else { },前两个比较都不返回 true 时(也就是玩家尚未猜对,但是还有机会)才会执行这里的代码。在这个情况下,我们会告诉玩家他们猜错了,并执行另一个条件测试,判断并告诉玩家猜测的数字是高了还是低了。
  • 函数最后三行(26 - 28 行)是为下次猜测值提交做准备的。我们把guessCount 变量的值加 1,以使玩家消耗一次机会 (++ 是自增操作符,为自身加 1),然后我们把表单中文本域的值清空,重新聚焦于此,准备下一轮游戏。

事件(Event)

现在,我们有一个实现比较不错的checkGuess()函数了,但它现在什么事情也做不了,因为我们还没有调用它。 理想中,我们希望在点击“确定”按钮时调用它,为此,我们需要使用事件。 事件就是浏览器中发生的事儿,比如点击按钮、加载页面、播放视频,等等,我们可以通过调用代码来响应事件。 侦听事件发生的结构称为事件监听器(Event Listener),响应事件触发而运行的代码块被称为事件处理器(Event Handler)

checkGuess()函数后添加以下代码:

guessSubmit.addEventListener('click', checkGuess);

这里我们为guessSubmit按钮添加了一个事件监听器。addEventListener()方法包含两个可输入值(称为“参数”(argument)),监听事件的类型(本例中为“click”),和当事件发生时我们想要执行的代码(本例中为checkGuess()函数)。注意,addEventListener()中作为参数的函数名不加括号。

现在,保存代码并刷新页面,示例应该能够工作了,但还不够完善。 现在唯一的问题是,如果玩家猜对或游戏次数用完,游戏将出错,因为我们尚未定义游戏结束时应运行的setGameOver()函数。 现在,让我们补全所缺代码,并完善示例功能。

补全游戏功能

在代码最后添加一个setGameOver()函数,然后我们一起来看看它:

function setGameOver() {
  guessField.disabled = true;
  guessSubmit.disabled = true;
  resetButton = document.createElement('button');
  resetButton.textContent = 'Start new game';
  document.body.appendChild(resetButton);
  resetButton.addEventListener('click', resetGame);
}
  • 前两行通过将disable属性设置为true来禁用表单文本输入和按钮。 这样做是必须的,否则用户就可以在游戏结束后提交更多的猜测,游戏的规则将遭到破坏。
  • 接下来的三行创建一个新的<button>元素,设置它的文本为“开始新游戏”,并把它添加到当前HTML的底部。
  • 最后一行在新按钮上设置了一个事件监听器,当它被点击时,一个名为resetGame()的函数被将被调用。

现在我们需要定义resetGame()这个函数,依然放到代码底部:

function resetGame() {
  guessCount = 1;

  var resetParas = document.querySelectorAll('.resultParas p');
  for (var i = 0 ; i < resetParas.length ; i++) {
    resetParas[i].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。
  • 清除所有信息段落。
  • 删除重置按钮
  • 启用表单元素,清空文本域并聚焦于此,准备接受新猜测的数字。
  • 删除lastResult段落的背景颜色。
  • 生成一个新的随机数,这样就可以猜测新的数字了!

此刻一个能功能完善的(简易版)游戏就完成了。恭喜!

我们现在来讨论下其他很重要的代码功能,你可能已经看到过,但是你可能没有意识到这一点。

循环(Loop)

上面代码中有一部分需要我们仔细研读,那就是 for 循环。 循环是一个非常重要的编程概念,它让你能够重复运行一段代码,直到满足某个条件为止。

首先,请再次转到 浏览器开发工具 JavaScript 控制台 然后输入以下内容:

for (var i = 1 ; i < 21 ; i++) { console.log(i) }

发生了什么? 控制台中打印出了数字1到20。 这正是循环所为。一个 for 循环需要三个输入值(参数):

  1. 起始值:本例中我们从1开始计数,但其他任意数字也可以。 i也可以用任何你喜欢的名字替换,但一般约定用i,因为它很短,且易于记忆。
  2. 退出条件:这里我们指定i < 21,直到i不再小于 21 前循环将继续。当 i 达到 21 时,循环将退出
  3. 增加器:我们指定 i++,意思是向 i 加 1。i 值的每次变动都会引发循环的执行,直到 i 值等于 21(如前文所讲)。为简化问题,在本例中,我们使用console.log()在控制台打印出每次迭代时变量 i 的值。

现在让我们来观察猜数字游戏中的循环——resetGame() 函数中可以找到以下内容:

var resetParas = document.querySelectorAll('.resultParas p');
for (var i = 0 ; i < resetParas.length ; i++) {
  resetParas[i].textContent = '';
}

这段代码通过 querySelectorAll() 方法创建了一个包含 <div class="resultParas"> 内所有段落的变量,然后通过循环迭代,删除每个段落的文本内容。

浅谈对象(Object)

在讨论前最后再改进一波。 在 var resetButton(靠近js代码顶端)下方添加下面一行内容,然后保存文件:

guessField.focus();

这一行通过focus() 方法让光标在页面加载完毕时自动放置于 <input>  输入框内,这意味着玩家可以马上开始第一次猜测,而无需点击输入框。 这只是一个小的改进,却提高了可用性——为使用户能投入游戏提供一个良好的视觉线索。

我们来分析一下这里的更多细节。JavaScript 中,一切都是对象。对象是存储在单个分组中的相关功能的集合。你可以创建自己的对象,但这是较高阶的知识,我们今后才会谈及。现在,我们仅仅简要讨论浏览器内置的对象,它们已经能够让你做许多有用的事情。

在本示例的特定情况下,我们首先创建一个 guessField 变量,用于存储对 HTML 中的文本输入表单域的引用,在顶部附近的变量声明中可以找到以下行:

var guessField = document.querySelector('.guessField');

使用document对象的querySelector()方法可以获得这个引用。querySelector()需要一个信息——用一个 CSS选择器 可以选中需要引用的元素。

因为guessField现在包含一个指向<input>元素的引用,它现在就能够访问一系列的属性(存储于对象内部的基础变量,其中一些的值无法改变)和方法(存储在对象内部的基础函数)。focus()<input>元素可用方法之一,因此我们可以使用这行代码将光标聚焦于此文本框上︰

guessField.focus();

不包含对表单元素引用的变量不提供focus()方法。例如,引用<p>元素的guesses变量,包含一个数字的guessCount变量。

操作浏览器对象

浏览器对象如何使用呢,下面我们来小试牛刀。

  1. 首先在浏览器中打开你的程序。
  2. 接下来打开浏览器开发者工具, 并且切换到 JavaScript 控制台的标签页。
  3. 输入 guessField ,控制台将会显示此变量包含一个<input> 元素。同时控制台还能自动补全运行环境中对象的名字,包括你的变量!
  4. 现在输入下面的代码:
    guessField.value = 'Hello';
    value 属性表示当前文本区域中输入的值。 在输入这条指令后,你将看到文本域区中的文本被我们修改了!
  5. 现在试试输入 guesses 然后回车。控制台会显示一个包含 <p> 元素的变量。
  6. 现在试试输入下面这一行:
    guesses.value
    浏览器会返回 undefined,因为段落中并不存在 value
  7. 为了改变段落中的文本内容, 你需要用 textContent 属性来代替 value。试试这个:
    guesses.textContent = '我的段落在哪里?';
  8. 下面是一些有趣的东西。 尝试依次输入下面几行:
    guesses.style.backgroundColor = 'yellow';
    guesses.style.fontSize = '200%';
    guesses.style.padding = '10px';
    guesses.style.boxShadow = '3px 3px 6px black';
  9. 页面上的每个元素都有一个 style 属性,它本身包含一个对象,其属性包含应用于该元素的所有内联 CSS 样式。 让我们可以使用 JavaScript 在元素上动态设置新的 CSS 样式。

大功告成...

这个示例已经构建完毕,做得好!来尝试运行一下最终的代码,或者 看看我们的最终版本如果你的版本无法正常工作,请对照 源代码 进行检查。

本章目录