用猜数字游戏,一口气掌握 JavaScript 核心知识点(附完整代码)
从一个简单小游戏开始,理解变量、函数、DOM 操作、事件监听、数组、本地状态管理......
这篇文章会带你亲手实现一个"猜数字"游戏,并拆解每一行代码背后的知识。
为什么第一个项目要做猜数字?
猜数字游戏麻雀虽小,五脏俱全。它包含了:
- 变量声明与作用域 (
const/let) - 随机数生成 (
Math.random) - 函数拆分与职责分离
- 输入处理与类型转换
- 条件判断与早返回
- 数组操作与状态更新
- DOM 查询、修改、显隐、禁用
- 事件监听(点击、键盘回车)
- 模板字符串
- 重置游戏与初始化
学完这个项目,你就能独立完成很多类似的交互小工具。
第一步:搭建界面(HTML + CSS)
我们先写好基础结构:一个数字输入框、两个按钮("猜"和"新游戏"),以及用于显示提示、次数、历史记录的区域。样式使用简单的居中卡片,保证易读。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>猜数字游戏|学习 JavaScript</title>
<style>
* {
box-sizing: border-box;
user-select: none;
}
body {
background: linear-gradient(145deg, #1e293b 0%, #0f172a 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: system-ui, 'Segoe UI', 'Roboto', sans-serif;
margin: 0;
padding: 20px;
}
.game-container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(8px);
border-radius: 2rem;
padding: 2rem;
width: 100%;
max-width: 500px;
box-shadow: 0 25px 45px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.2);
}
h1 {
text-align: center;
color: #facc15;
margin-top: 0;
font-size: 2rem;
}
.input-area {
display: flex;
gap: 12px;
margin: 24px 0;
}
input {
flex: 1;
padding: 12px 16px;
font-size: 1.2rem;
border: none;
border-radius: 60px;
background: #1e293b;
color: white;
text-align: center;
outline: none;
transition: 0.2s;
}
input:focus {
outline: 2px solid #facc15;
}
button {
background: #facc15;
border: none;
padding: 0 24px;
border-radius: 60px;
font-weight: bold;
font-size: 1rem;
cursor: pointer;
transition: 0.2s;
color: #0f172a;
}
button:active {
transform: scale(0.96);
}
.info-card {
background: #0f172a80;
border-radius: 1.5rem;
padding: 1rem;
margin: 20px 0;
text-align: center;
}
.message {
font-size: 1.2rem;
font-weight: bold;
color: #facc15;
min-height: 3rem;
}
.stats {
display: flex;
justify-content: space-between;
font-size: 0.9rem;
color: #cbd5e1;
}
.history {
background: #0f172a;
border-radius: 1rem;
padding: 8px 12px;
font-family: monospace;
word-break: break-all;
}
.new-game {
width: 100%;
margin-top: 12px;
background: #3b82f6;
color: white;
}
</style>
</head>
<body>
<div class="game-container">
<h1>🔢 猜数字</h1>
<p style="text-align:center; color:#94a3b8;">我已经想好了一个 1~100 之间的整数</p>
<div class="input-area">
<input type="number" id="guessInput" placeholder="输入你的猜测" min="1" max="100">
<button id="guessBtn">猜</button>
</div>
<div class="info-card">
<div class="message" id="message">✨ 点击「新游戏」开始</div>
<div class="stats">
<span>🎯 尝试次数:<span id="attemptCount">0</span></span>
<span>📜 历史记录:<span id="historyList">---</span></span>
</div>
</div>
<button id="newGameBtn" class="new-game">🔄 新游戏</button>
</div>
<script>
// ---------- 所有 JS 代码写在这里 ----------
// 下文会完整展示
</script>
</body>
</html>
第二步:JavaScript 核心知识点逐项拆解
2.1 变量声明:const 与 let 的用法
在脚本开头,我们首先获取 DOM 元素。这些引用在游戏过程中不会改变,所以用 const 声明:
js
const guessInput = document.getElementById('guessInput');
const guessBtn = document.getElementById('guessBtn');
const newGameBtn = document.getElementById('newGameBtn');
const messageDiv = document.getElementById('message');
const attemptSpan = document.getElementById('attemptCount');
const historySpan = document.getElementById('historyList');
而游戏状态 (目标数字、尝试次数、历史数组)会不断变化,因此使用 let:
js
let targetNumber = 0;
let attempts = 0;
let guessHistory = [];
规则 :DOM 引用用
const,业务状态用let。
2.2 随机数生成:Math.random() + Math.floor()
我们需要生成 1~100 的随机整数。公式是固定的:
js
function randomInt1To100() {
return Math.floor(Math.random() * 100) + 1;
}
Math.random()→ [0, 1)* 100→ [0, 100)Math.floor()→ 向下取整,得到 0~99+ 1→ 1~100
2.3 函数拆分(单一职责)
我们把不同任务拆成独立函数:
randomInt1To100():只负责随机数生成。resetGame():重置游戏状态、清空界面、启用控件。validateGuess(rawValue):校验输入是否合法,返回错误文本或null。handleGuess():完整的一次猜测流程。
这样主流程非常清晰,也便于测试和修改。
2.4 输入处理与类型转换
从 <input> 拿到的值是字符串,必须转成数字才能比较:
js
const rawValue = guessInput.value;
if (rawValue.trim() === '') {
return '不能为空';
}
const value = Number(rawValue);
if (isNaN(value) || !Number.isInteger(value)) {
return '请输入整数';
}
if (value < 1 || value > 100) {
return '数字必须在 1~100 之间';
}
这里使用了 Number.isInteger() 确保不是小数。
2.5 条件判断与"早返回"
在 handleGuess 中,先检查游戏是否已结束(猜中后禁用按钮),再使用校验函数:
js
if (guessBtn.disabled) {
messageDiv.textContent = '游戏已结束,请点击「新游戏」';
return;
}
const error = validateGuess(guessInput.value);
if (error) {
messageDiv.textContent = error;
return;
}
这种"早返回"写法可以大幅减少嵌套 if,让代码更平直。
2.6 状态更新:计数与历史数组
每次有效猜测后:
js
attempts++;
guessHistory.push(guessNumber);
attemptSpan.textContent = attempts;
historySpan.textContent = guessHistory.join(', ');
用 join(', ') 把数组转成易读的字符串,没有显式使用循环,但内部已经遍历。
2.7 DOM 操作:修改内容、禁用/启用控件、显示/隐藏
- 修改文本:
.textContent - 禁用输入框 / 按钮:
.disabled = true - 聚焦输入框:
.focus() - 按钮显示/隐藏(本例使用了
.style.display不过为了简洁可直接用禁用)
在猜中时,我们禁用猜测按钮和输入框;重置时再启用。
2.8 事件监听
绑定三个事件:
js
guessBtn.addEventListener('click', handleGuess);
newGameBtn.addEventListener('click', resetGame);
guessInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !guessBtn.disabled) {
handleGuess();
}
});
注意:回车触发前要检查游戏是否结束(!guessBtn.disabled),避免猜中后还能继续猜。
2.9 模板字符串(反引号)
动态拼接提示信息非常方便:
js
messageDiv.textContent = `🎉 恭喜!猜中了!共用 ${attempts} 次`;
2.10 初始化时机
页面加载后必须立即让游戏就绪,调用 resetGame() 生成随机数、清空界面。
第三步:完整代码(复制即用)
下面是整合后的完整 index.html(包含样式和所有 JS)。你可以保存为 .html 文件,用浏览器打开直接玩。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>猜数字游戏|JavaScript 核心练习</title>
<style>
* { box-sizing: border-box; user-select: none; }
body {
background: linear-gradient(145deg, #1e293b 0%, #0f172a 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: system-ui, 'Segoe UI', sans-serif;
margin: 0;
padding: 20px;
}
.game-container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(8px);
border-radius: 2rem;
padding: 2rem;
width: 100%;
max-width: 500px;
box-shadow: 0 25px 45px rgba(0,0,0,0.3);
border: 1px solid rgba(255,255,255,0.2);
}
h1 { text-align: center; color: #facc15; margin-top: 0; font-size: 2rem; }
.input-area { display: flex; gap: 12px; margin: 24px 0; }
input {
flex: 1; padding: 12px 16px; font-size: 1.2rem;
border: none; border-radius: 60px; background: #1e293b;
color: white; text-align: center; outline: none;
}
input:focus { outline: 2px solid #facc15; }
button {
background: #facc15; border: none; padding: 0 24px;
border-radius: 60px; font-weight: bold; font-size: 1rem;
cursor: pointer; transition: 0.2s; color: #0f172a;
}
button:active { transform: scale(0.96); }
button:disabled { opacity: 0.5; transform: none; cursor: not-allowed; }
.info-card {
background: #0f172a80; border-radius: 1.5rem; padding: 1rem;
margin: 20px 0; text-align: center;
}
.message { font-size: 1.2rem; font-weight: bold; color: #facc15; min-height: 3rem; }
.stats { display: flex; justify-content: space-between; font-size: 0.9rem; color: #cbd5e1; }
.history {
background: #0f172a; border-radius: 1rem; padding: 8px 12px;
font-family: monospace; word-break: break-all;
}
.new-game { width: 100%; margin-top: 12px; background: #3b82f6; color: white; }
</style>
</head>
<body>
<div class="game-container">
<h1>🔢 猜数字</h1>
<p style="text-align:center; color:#94a3b8;">我已经想好了一个 1~100 之间的整数</p>
<div class="input-area">
<input type="number" id="guessInput" placeholder="输入你的猜测" min="1" max="100">
<button id="guessBtn">猜</button>
</div>
<div class="info-card">
<div class="message" id="message">✨ 点击「新游戏」开始</div>
<div class="stats">
<span>🎯 尝试次数:<span id="attemptCount">0</span></span>
<span>📜 历史记录:<span id="historyList">---</span></span>
</div>
</div>
<button id="newGameBtn" class="new-game">🔄 新游戏</button>
</div>
<script>
// ---------- DOM 元素 ----------
const guessInput = document.getElementById('guessInput');
const guessBtn = document.getElementById('guessBtn');
const newGameBtn = document.getElementById('newGameBtn');
const messageDiv = document.getElementById('message');
const attemptSpan = document.getElementById('attemptCount');
const historySpan = document.getElementById('historyList');
// ---------- 游戏状态 ----------
let targetNumber = 0;
let attempts = 0;
let guessHistory = [];
// ---------- 工具函数 ----------
function randomInt1To100() {
return Math.floor(Math.random() * 100) + 1;
}
// 校验输入,返回错误字符串或 null
function validateGuess(rawValue) {
const trimmed = rawValue.trim();
if (trimmed === '') return '请输入数字';
const num = Number(trimmed);
if (isNaN(num)) return '必须是数字';
if (!Number.isInteger(num)) return '请输入整数';
if (num < 1 || num > 100) return '数字必须在 1~100 之间';
return null; // 合法
}
// 更新界面:显示次数、历史记录
function updateUI() {
attemptSpan.textContent = attempts;
if (guessHistory.length === 0) {
historySpan.textContent = '---';
} else {
historySpan.textContent = guessHistory.join(', ');
}
}
// 重置游戏(新游戏)
function resetGame() {
targetNumber = randomInt1To100();
attempts = 0;
guessHistory = [];
updateUI();
messageDiv.textContent = '✨ 新游戏开始!输入 1~100 的数字吧';
guessInput.value = '';
guessInput.disabled = false;
guessBtn.disabled = false;
guessInput.focus();
}
// 核心逻辑:处理一次猜测
function handleGuess() {
// 1. 游戏是否已结束(猜中后按钮被禁用)
if (guessBtn.disabled) {
messageDiv.textContent = '游戏已结束,请点击「新游戏」';
return;
}
// 2. 校验输入
const error = validateGuess(guessInput.value);
if (error) {
messageDiv.textContent = error;
guessInput.value = '';
guessInput.focus();
return;
}
// 3. 转数字并记录
const guessNumber = Number(guessInput.value.trim());
attempts++;
guessHistory.push(guessNumber);
updateUI();
// 4. 比较并反馈
let feedback = '';
if (guessNumber > targetNumber) {
feedback = '📈 猜大了,再试试看!';
} else if (guessNumber < targetNumber) {
feedback = '📉 猜小了,再试试看!';
} else {
feedback = `🎉 恭喜!猜中了!共用 ${attempts} 次 🎉`;
// 游戏胜利:禁用输入框和按钮
guessInput.disabled = true;
guessBtn.disabled = true;
messageDiv.textContent = feedback;
return;
}
messageDiv.textContent = feedback;
guessInput.value = '';
guessInput.focus();
}
// ---------- 事件绑定 ----------
guessBtn.addEventListener('click', handleGuess);
newGameBtn.addEventListener('click', resetGame);
guessInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !guessBtn.disabled) {
handleGuess();
}
});
// ---------- 页面初始化 ----------
resetGame();
</script>
</body>
</html>
第四步:你能从这个项目学到什么?
完成这个项目后,你就不再是只懂语法的"纸上程序员"了。你已经能够:
- 独立拆分函数,让代码可读、可维护。
- 熟练使用
const/let管理状态。 - 自己写随机数、处理用户输入、校验边界。
- 操作 DOM:修改文本、禁用控件、动态刷新数组数据。
- 使用事件监听让页面拥有完整交互。
更重要的是,你学会了通过一个小项目把零散知识点串起来------这才是真正的"会用"。
下一步你可以做什么?
- 增加难度选择(例如 1--50 / 1--200)
- 加入"剩余机会"限制(最多 8 次,用完显示失败)
- 把最佳成绩保存到
localStorage - 拆分
index.html中的 JS 到独立的main.js(模块化思维)
只要你完成了上面任何一个扩展,你的 JS 能力就会再上一个台阶。