下面我们将会通过一个小案例——猜数字游戏,来直观地感受一下如何让JavaScript完成任务。
设计要求
假设你的老板给你布置了以下游戏设计任务要求:
我想让你开发一个猜数字游戏。游戏应随机选择一个 100 以内的自然数, 然后邀请玩家在 10 轮以内猜出这个数字。每轮后都应告知玩家的答案正确与否,如果出错了,则告诉他数字是低了还是高了。并且应显示出玩家前一轮所猜的数字。一旦玩家猜对,或者用尽所有机会,游戏将结束。游戏结束后,可以让玩家选择再次开始。
看到这个要求,首先我们要做的是将其分解成简单的可操作的任务,尽可能从程序员的思维去思考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 1. 随机生成一个 100 以内的自然数。 2. 记录玩家当前的轮数。从 1 开始。 3. 为玩家提供一种猜测数字的方法。 4. 一旦有结果提交,先将其记录下来,以便用户可以看到他们先前的猜测。 5. 然后检查它是否正确。 6. 如果正确: 1. 显示祝贺消息。 2. 阻止玩家继续猜测(这会使游戏混乱)。 3. 显示控件允许玩家重新开始游戏。`在这里插入代码片` 7. 如果出错,并且玩家有剩余轮次: 1. 告诉玩家他们错了。 2. 允许他们输入另一个猜测。 3. 轮数加 1。 8. 如果出错,并且玩家没有剩余轮次: 1. 告诉玩家游戏结束。 2. 阻止玩家继续猜测(这会使游戏混乱)。 3. 显示控件允许玩家重新开始游戏。 9. 一旦游戏重启,确保游戏的逻辑和UI完全重置,然后返回步骤1。
|
代码实现
让我们继续,看看我们如何将上面这些步骤转换为代码,构建这个示例,从而探索 JavaScript 的功能。
初始设置
首先是静态页面的实现,一个标题,一段游戏说明,和一个用于输入猜测的表单(此时表单不会执行任何操作)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <!DOCTYPE html> <html> <head> <meta charset="utf-8">
<title>猜数字游戏</title>
</head>
<body> <h1>猜数字游戏</h1>
<p>我刚才随机选定了一个100以内的自然数。看你能否在 10 次以内猜中它。每次我都会告诉你所猜的结果是高了还是低了。</p>
<div class="form"> <label for="guessField">请猜数: </label> <input type="text" id="guessField" class="guessField"> <input type="submit" value="确定" class="guessSubmit"> </div>
<div class="resultParas"> <p class="guesses"></p> <p class="lastResult"></p> <p class="lowOrHi"></p> </div>
</body> </html>
|
再为其添加一些CSS样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <style> html { font-family: sans-serif; }
body { width: 50%; max-width: 800px; min-width: 480px; margin: 0 auto; }
.lastResult { color: white; padding: 3px; } </style>
|
最后我们将在底部的 <script>
元素中添加新的代码:
添加变量以保存数据
首先,在 <script>
元素中添加以下代码:
1 2 3 4 5 6 7 8 9 10 11
| 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
(旧代码中使用 var
)和一个名字来创建变量。常量用于存储不希望更改的数据,用关键字 const
创建,本例中用常量来保存对界面元素的引用。界面元素的文字可能会改变,但引用是不变的。
可以使用等号(=)和一个值来为变量赋值。
在我们的示例中:
- 我们用数学算法得出一个 1 到 100 之间的随机数,并赋值给第一个变量(
randomNumber
)。
- 接下来的三个常量均存储着一个引用,分别指向HTML结果段落中某个元素,用于在代码后面段落中插入值:
1 2 3
| <p class="guesses"></p> <p class="lastResult"></p> <p class="lowOrHi"></p>
|
- 接下来的两个常量存储对表单文本输入和提交按钮的引用,并用于控制以后提交猜测:
1 2 3
| <label for="guessField">请猜数:</label> <input type="text" id="guessField" class="guessField"> <input type="submit" value="确定" class="guessSubmit">
|
- 倒数第二个变量存储一个计数器并初始化为 1(用于跟踪玩家猜测的次数),最后一个变量存储对重置按钮的引用,这个按钮尚不存在(但稍后就有了)。
函数,运算符,条件语句
与其他编程语言一样,JavaScript也有函数,运算符,与条件语句。
下面我们在之前的代码后面添加一个 checkGuess()
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| function checkGuess() { let 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,即玩家是不是第一次猜数字:
如果是, 我们让 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()
函数后添加以下代码:
1
| guessSubmit.addEventListener('click', checkGuess);
|
这里为 guessSubmit
按钮添加了一个事件监听器。addEventListener()
方法包含两个可输入值(称为“参数”(argument)),监听事件的类型(本例中为“click
”),和当事件发生时我们想要执行的代码(本例中为 checkGuess()
函数)。注意,addEventListener()
中作为参数的函数名不加括号。
现在,保存代码并刷新页面,示例应该能够工作了,但还不够完善。 现在唯一的问题是,如果玩家猜对或游戏次数用完,游戏将出错,因为我们尚未定义游戏结束时应运行的setGameOver()
函数。 现在,让我们补全所缺代码,并完善示例功能。
补全游戏功能
在代码最后添加一个 setGameOver()
函数,然后我们一起来看看它:
1 2 3 4 5 6 7 8
| function setGameOver() { guessField.disabled = true; guessSubmit.disabled = true; resetButton = document.createElement('button'); resetButton.textContent = '开始新游戏'; document.body.appendChild(resetButton); resetButton.addEventListener('click', resetGame); }
|
- 前两行通过将
disable
属性设置为 true
来禁用表单文本输入和按钮。 这样做是必须的,否则用户就可以在游戏结束后提交更多的猜测,游戏的规则将遭到破坏。
- 接下来的三行创建一个新的
<button>
元素,设置它的文本为“开始新游戏”,并把它添加到当前 HTML 的底部。
- 最后一行在新按钮上设置了一个事件监听器,当它被点击时,一个名为
resetGame()
的函数被将被调用。
现在我们需要定义 resetGame()
这个函数,依然放到代码底部:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function resetGame() { guessCount = 1;
const resetParas = document.querySelectorAll('.resultParas p'); for (let 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 循环。 循环是一个非常重要的编程概念,它让你能够重复运行一段代码,直到满足某个条件为止。
现在让我们来观察猜数字游戏中的循环 —— resetGame()
函数中可以找到以下内容:
1 2 3 4
| let resetParas = document.querySelectorAll('.resultParas p'); for (let i = 0 ; i < resetParas.length ; i++) { resetParas[i].textContent = ''; }
|
这段代码通过 querySelectorAll()
方法创建了一个包含 <div class="resultParas">
内所有段落的变量,然后通过循环迭代,删除每个段落的文本内容。
浅谈对象(Object)
在讨论前最后再改进一波。 在 let resetButton
(脚本顶端部分)下方添加下面一行内容,然后保存文件:
这一行通过 focus()
方法让光标在页面加载完毕时自动放置于 <input>
输入框内,这意味着玩家可以马上开始第一次猜测,而无需点击输入框。 这只是一个小的改进,却提高了可用性——为使用户能投入游戏提供一个良好的视觉线索。
深入分析一下。JavaScript 中一切都是对象。对象是存储在单个分组中的相关功能的集合。可以创建自己的对象,但这是较高阶的知识,我们今后才会谈及。现在,仅需简要讨论浏览器内置的对象,它们已经能够做许多有用的事情。
在本示例的特定情况下,我们首先创建一个 guessField
常量来存储对 HTML 中的文本输入表单域的引用,在文档顶部的声明区域中可以找到以下行:
1
| const guessField = document.querySelector('.guessField');
|
使用 document
对象的 querySelector()
方法可以获得这个引用。querySelector()
需要一个信息——用一个 CSS选择器 可以选中需要引用的元素。
因为 guessField
现在包含一个指向 <input>
元素的引用,它现在就能够访问一系列的属性(存储于对象内部的基础变量,其中一些的值无法改变)和方法(存储在对象内部的基础函数)。focus()
是 <input>
元素可用方法之一,因此我们可以使用这行代码将光标聚焦于此文本框上︰
不包含对表单元素引用的变量不提供 focus()
方法。例如,引用 <p>
元素的 guesses
常量,包含一个数字的 guessCount
变量。
操作浏览器对象
浏览器对象如何使用呢,下面我们来小试牛刀。
- 首先在浏览器中打开你的程序。
- 接下来打开 浏览器开发者工具, 并且切换到 JavaScript 控制台的标签页。
- 输入
guessField
,控制台将会显示此变量包含一个 <input>
元素。同时控制台还能自动补全运行环境中对象的名字,包括你的变量!
- 现在输入下面的代码:
value
属性表示当前文本区域中输入的值。 在输入这条指令后,你将看到文本域区中的文本被我们修改了!1
| guessField.value = 'Hello';
|
- 现在试试输入
guesses
然后回车。控制台会显示一个包含 <p>
元素的变量。
- 现在试试输入下面这一行:浏览器会返回
undefined
,因为段落中并不存在 value
。
- 为了改变段落中的文本内容, 你需要用
textContent
属性来代替 value
。试试这个:
1
| guesses.textContent = '我的段落在哪里?';
|
- 下面是一些有趣的东西。 尝试依次输入下面几行:
1 2 3 4
| guesses.style.backgroundColor = 'yellow'; guesses.style.fontSize = '200%'; guesses.style.padding = '10px'; guesses.style.boxShadow = '3px 3px 6px black';
|
- 页面上的每个元素都有一个
style
属性,它本身包含一个对象,其属性包含应用于该元素的所有内联 CSS 样式。 让我们可以使用 JavaScript 在元素上动态设置新的 CSS 样式。
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
| <!DOCTYPE html> <html> <head> <meta charset="utf-8">
<title>猜数字游戏</title>
<style> html { font-family: sans-serif; }
body { width: 50%; max-width: 800px; min-width: 480px; margin: 0 auto; }
.lastResult { color: white; padding: 3px; } </style> </head>
<body> <h1>猜数字游戏</h1>
<p>我刚才随机选定了一个100以内的自然数。看你能否在 10 次以内猜中它。每次我都会告诉你所猜的结果是高了还是低了。</p>
<div class="form"> <label for="guessField">请猜数: </label> <input type="text" id="guessField" class="guessField"> <input type="submit" value="确定" class="guessSubmit"> </div>
<div class="resultParas"> <p class="guesses"></p> <p class="lastResult"></p> <p class="lowOrHi"></p> </div>
<script>
// 开始编写 JavaScript 代码 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; guessField.focus();
function checkGuess() { let 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(); // 获取焦点 } guessSubmit.addEventListener('click', checkGuess);
function setGameOver() { guessField.disabled = true; guessSubmit.disabled = true; resetButton = document.createElement('button'); resetButton.textContent = '开始新游戏'; document.body.appendChild(resetButton); resetButton.addEventListener('click', resetGame); } function resetGame() { guessCount = 1;
const resetParas = document.querySelectorAll('.resultParas p'); for (let 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; } </script> </body> </html>
|