用猜数字游戏,一口气掌握 JavaScript 核心知识点(附完整代码)

用猜数字游戏,一口气掌握 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 变量声明:constlet 的用法

在脚本开头,我们首先获取 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 能力就会再上一个台阶。

相关推荐
忆往wu前1 小时前
从0到1一步步拆解搭建,梳理一个 Vue3 简易图书后台全开发流程
前端·javascript·vue.js
木斯佳1 小时前
前端八股文面经大全:字节抖音前端三面(2026-04-27)·面经深度解析
前端·面试·笔试·八股·面经
shao9185162 小时前
第3章(2)——使用Gradio JavaScript Client
javascript·node.js·cdn·gradio·job·events·playcode
光影少年2 小时前
大屏页面,一次多个请求,请求加密导致 点击 全局时间选择器 时出现卡顿咋解决(面板收起会延迟1~2秒)
前端·javascript·vue.js·学习·前端框架·echarts·reactjs
Mr.mjw2 小时前
vue中封装一个环形进度条组件,根据外部盒子大小自适应变化
前端·javascript·vue.js
无心使然2 小时前
Openlayers调用ArcGis影像服务之一动态地图、地图切片(/exportImage)
前端·javascript·数据可视化
唯火锅不可辜负2 小时前
uniapp开发公众号订阅功能踩坑小记
前端·vue.js
opteOG3 小时前
游览器跨域问题详解
前端
SameX3 小时前
后台 GPS 记录从半天掉电 30% 到全天 8%,我的三版方案演进
前端