精选5个适合JavaScript初学者的练手小项目案例,涵盖计数器、待办清单、猜数字游戏、字数统计器与选项卡切换,解析核心DOM操作、事件处理流程与避坑指南。
适合JavaScript初学者的5个练手小项目案例
时间:2026-10-30
浏览:492
文章目录
学习 JavaScript 最直接且高效的路径,是通过体量轻、逻辑完整的小练习,将语法概念与真实的 DOM 交互绑定起来。初学者在看完基础语法后,往往在面对“网页中的按钮怎么起作用”、“输入框里的内容怎么取出来”、“如何动态加一行内容”时感到无从下手。本文选取了 5 个梯度连贯的练手案例:简易计数器、待办事项清单、猜数字游戏、实时文本字数统计器与原生选项卡切换。这些项目不需要配置复杂脚手架或打包工具,用一个 HTML 文件加一个 JS 脚本即可运行,能够帮助初学者扎实掌握事件监听、DOM 元素增删改、条件分支及数据与界面同步的核心方法。
案例一:简易计数器(Counter)
计数器是初学者接触 JavaScript 交互最理想的第一个项目。它的业务逻辑极其聚焦:页面展示一个当前数值,用户点击“增加”、“减少”或“重置”按钮,数值随之改变,并将最新的计算结果重新渲染到页面上。这个项目帮助你理解什么叫“内存中的变量状态”以及“如何将变量状态同步到网页文本中”。
1. 核心掌握的 API 与概念
document.getElementById()或document.querySelector():从 DOM 树中抓取需要绑定的按钮与展示文本的容器。element.addEventListener('click', handler):为按钮注册点击事件监听器。element.textContent:安全且直接地修改元素内部的纯文本内容。
2. 完整可执行代码
<!-- index.html 结构 -->
<div class="counter-box">
<h2 id="countDisplay">0</h2>
<button id="decreaseBtn">-1</button>
<button id="resetBtn">重置</button>
<button id="increaseBtn">+1</button>
</div>
<script>
// 1. 获取 DOM 节点
const countDisplay = document.getElementById('countDisplay');
const decreaseBtn = document.getElementById('decreaseBtn');
const resetBtn = document.getElementById('resetBtn');
const increaseBtn = document.getElementById('increaseBtn');
// 2. 在内存中声明状态变量
let count = 0;
// 3. 编写专门用于更新界面的渲染函数
function updateDisplay() {
countDisplay.textContent = count;
// 根据数值正负动态改变文字颜色
if (count > 0) {
countDisplay.style.color = '#438A80'; // 正数显示青绿
} else if (count < 0) {
countDisplay.style.color = '#E53E3E'; // 负数显示红色
} else {
countDisplay.style.color = '#456581'; // 归零显示默认深蓝
}
}
// 4. 绑定点击事件
increaseBtn.addEventListener('click', () => {
count++;
updateDisplay();
});
decreaseBtn.addEventListener('click', () => {
count--;
updateDisplay();
});
resetBtn.addEventListener('click', () => {
count = 0;
updateDisplay();
});
</script>
countDisplay.textContent++。由于 textContent 得到的是字符串类型(String),直接做自增自减容易导致类型转换混乱或代码难以维护。规范的做法是维护一个纯粹的数字变量(如 let count = 0),数值计算完成后再写回 DOM。
案例二:待办事项清单(To-Do List)
待办事项清单是前端面试和练习中经典的“进阶里程碑”。它涉及完整的 DOM 节点生命周期:用户在输入框输入文本后点击添加,页面动态创建一条包含文本与操作按钮的 <li> 节点;点击完成可以为文本划横线,点击删除可以将整行节点从 DOM 中移除。
1. 完整实现代码
<!-- HTML 结构 -->
<form id="todoForm">
<input type="text" id="todoInput" placeholder="请输入任务内容..." autocomplete="off" />
<button type="submit">添加</button>
</form>
<ul id="todoList"></ul>
<script>
const todoForm = document.getElementById('todoForm');
const todoInput = document.getElementById('todoInput');
const todoList = document.getElementById('todoList');
// 1. 监听表单提交
todoForm.addEventListener('submit', (e) => {
e.preventDefault(); // 拦截默认页面刷新行为
const taskText = todoInput.value.trim();
if (!taskText) {
alert('任务内容不能为空!');
return;
}
// 2. 动态创建 li 元素
const li = document.createElement('li');
// 创建文字包裹标签
const span = document.createElement('span');
span.textContent = taskText;
li.appendChild(span);
// 创建删除按钮
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '删除';
deleteBtn.className = 'delete-btn';
li.appendChild(deleteBtn);
// 3. 挂载到列表中
todoList.appendChild(li);
// 4. 重置输入框
todoInput.value = '';
todoInput.focus();
});
// 2. 使用事件委托处理点击事件(完成与删除)
todoList.addEventListener('click', (e) => {
const target = e.target;
// 点击删除按钮时,移除对应的整行 li
if (target.classList.contains('delete-btn')) {
const parentLi = target.parentElement;
todoList.removeChild(parentLi);
}
// 点击任务文字时,切换完成状态样式
else if (target.tagName === 'SPAN') {
target.style.textDecoration =
target.style.textDecoration === 'line-through' ? 'none' : 'line-through';
}
});
</script>
appendChild 时都给新按钮写一个 button.addEventListener('click', ...),当列表条目增加时,会创建大量闭包和事件监听器,消耗内存;更麻烦的是重新渲染或清空时容易造成内存泄漏。把监听器挂载在静态的父级 <ul> 上,通过 e.target 判断触发源,是前端通用且专业的标准做法。
案例三:简易数字猜大小游戏(Number Guessing Game)
该项目围绕纯 JavaScript 的条件分支逻辑、数学计算和状态重置展开。系统在后台秘密生成一个 1 到 100 之间的随机整数,玩家在输入框输入猜测的数字,程序比对后输出“太大了”、“太小了”或“猜对了”,并记录玩家已经尝试的次数。
1. 核心技术点解析
Math.floor(Math.random() * 100) + 1:生成指定范围内的随机整数。parseInt(val, 10)或Number(val):将用户输入的文本类型显式转为数字类型。- 防呆边界校验:校验是否输入了非数字(
isNaN)或超出了 1~100 的有效范围。
2. 完整代码
<!-- HTML 结构 -->
<div class="game-box">
<p>系统已生成 1 到 100 之间的随机整数,请在下方输入猜测:</p>
<input type="number" id="guessInput" min="1" max="100" placeholder="1-100" />
<button id="guessBtn">确认猜测</button>
<button id="restartBtn" style="display:none;">重新开始</button>
<p id="feedbackText"></p>
<p id="attemptsText">已猜次数:0</p>
</div>
<script>
let targetNumber = generateTarget();
let attempts = 0;
let isGameOver = false;
const guessInput = document.getElementById('guessInput');
const guessBtn = document.getElementById('guessBtn');
const restartBtn = document.getElementById('restartBtn');
const feedbackText = document.getElementById('feedbackText');
const attemptsText = document.getElementById('attemptsText');
function generateTarget() {
return Math.floor(Math.random() * 100) + 1;
}
guessBtn.addEventListener('click', () => {
if (isGameOver) return;
// 获取并显式转换为整数
const userGuess = parseInt(guessInput.value, 10);
// 边界条件判断
if (isNaN(userGuess) || userGuess < 1 || userGuess > 100) {
feedbackText.textContent = '请输入 1 到 100 之间的有效数字!';
feedbackText.style.color = '#D97706';
return;
}
attempts++;
attemptsText.textContent = `已猜次数:${attempts}`;
// 核心分支判断
if (userGuess === targetNumber) {
feedbackText.textContent = `恭喜你!猜对了,数字就是 ${targetNumber}!`;
feedbackText.style.color = '#438A80';
isGameOver = true;
guessBtn.disabled = true;
restartBtn.style.display = 'inline-block';
} else if (userGuess > targetNumber) {
feedbackText.textContent = '太大了,再试一个更小的数字!';
feedbackText.style.color = '#456581';
} else {
feedbackText.textContent = '太小了,再试一个更大的数字!';
feedbackText.style.color = '#456581';
}
guessInput.value = '';
guessInput.focus();
});
restartBtn.addEventListener('click', () => {
targetNumber = generateTarget();
attempts = 0;
isGameOver = false;
guessBtn.disabled = false;
restartBtn.style.display = 'none';
feedbackText.textContent = '';
attemptsText.textContent = '已猜次数:0';
guessInput.value = '';
guessInput.focus();
});
</script>
案例四:实时文本字数统计器(Character Counter)
在各类社交媒体发布框或表单填写场景中,字符计数与超长警告是极常见的组件。该项目要求监听用户的键盘打字与粘贴输入,实时计算文本字符数和剩余可用字数,当接近或达到限制时,动态通过样式类切换给用户清晰的颜色警告。
1. 为什么必须使用 input 事件?
很多初学者会习惯性地监听 keydown 或 keyup。但如果用户通过鼠标右键菜单“粘贴”、直接将文字拖拽进文本域,或者通过中文输入法进行候选字选词,keydown 往往无法准确获取最终生效的文本内容。而原生的 input 事件是在文本框真实值发生任何变更时立即触发,能够全面覆盖打字、删除、粘贴、拖拽等全部交互场景。
2. 完整代码
<!-- HTML 结构 -->
<div class="counter-container">
<textarea id="contentArea" rows="5" maxlength="150" placeholder="请输入内容(上限 150 字)..."></textarea>
<div class="counter-info">
<span id="currentCount">0</span> / <span id="maxLimit">150</span> 字符
<span id="warningMsg" style="color: #E53E3E; margin-left: 10px; display: none;">已达最大字数限制!</span>
</div>
</div>
<script>
const contentArea = document.getElementById('contentArea');
const currentCount = document.getElementById('currentCount');
const maxLimit = document.getElementById('maxLimit');
const warningMsg = document.getElementById('warningMsg');
const MAX_CHARS = 150;
maxLimit.textContent = MAX_CHARS;
contentArea.addEventListener('input', () => {
const len = contentArea.value.length;
currentCount.textContent = len;
// 当输入达到或接近上限时改变提示视觉
if (len >= MAX_CHARS) {
currentCount.style.color = '#E53E3E';
currentCount.style.fontWeight = 'bold';
warningMsg.style.display = 'inline';
} else if (len >= MAX_CHARS * 0.9) {
// 达到90%阈值时给出黄色预警
currentCount.style.color = '#D97706';
currentCount.style.fontWeight = 'bold';
warningMsg.style.display = 'none';
} else {
currentCount.style.color = '#456581';
currentCount.style.fontWeight = 'normal';
warningMsg.style.display = 'none';
}
});
</script>
案例五:纯原生选项卡切换(Tab Switching)
选项卡(Tabs)是网页布局中最普遍的交互形式之一。点击上方的不同标签头,下方的内容区域实时切换展示。在这个项目中,初学者将学到前端经典的“排他思想”:先清除所有同级元素的激活样式,再单独为当前被选中的目标元素添加激活类;同时借助 HTML5 的 data-* 自定义属性,实现标签与对应内容块的精准关联。
1. 关键逻辑与排他思想
<!-- HTML 结构 -->
<div class="tabs-component">
<!-- 标签导航 -->
<div class="tab-headers" id="tabHeaders">
<button class="tab-btn active" data-target="tab-1">基础介绍</button>
<button class="tab-btn" data-target="tab-2">技术细节</button>
<button class="tab-btn" data-target="tab-3">参考资源</button>
</div>
<!-- 内容面板 -->
<div class="tab-contents">
<div class="tab-panel active" id="tab-1">这是【基础介绍】面板的内容,默认展示。</div>
<div class="tab-panel" id="tab-2" style="display:none;">这是【技术细节】面板,包含核心代码和实现步骤。</div>
<div class="tab-panel" id="tab-3" style="display:none;">这是【参考资源】面板,包含扩展阅读与文档链接。</div>
</div>
</div>
<script>
const tabHeaders = document.getElementById('tabHeaders');
const allButtons = tabHeaders.querySelectorAll('.tab-btn');
const allPanels = document.querySelectorAll('.tab-panel');
tabHeaders.addEventListener('click', (e) => {
// 确保点击的是标签按钮
if (!e.target.classList.contains('tab-btn')) return;
const clickedBtn = e.target;
const targetPanelId = clickedBtn.dataset.target; // 读取 data-target 属性
// 1. 排他处理:移除所有按钮的 active 类并隐藏所有面板
allButtons.forEach(btn => btn.classList.remove('active'));
allPanels.forEach(panel => {
panel.classList.remove('active');
panel.style.display = 'none';
});
// 2. 激活当前选中的按钮
clickedBtn.classList.add('active');
// 3. 显示对应的面板
const activePanel = document.getElementById(targetPanelId);
if (activePanel) {
activePanel.classList.add('active');
activePanel.style.display = 'block';
}
});
</script>
初学者练习小项目的实用建议
很多初学者在跟练项目时习惯于直接复制粘贴代码,或者只看懂了逻辑就不再动手。想要把代码真正转化成自己的肌肉记忆,建议按照以下三个步骤逐步推进:
- 先用自然语言理清数据流向:在写具体代码前,在纸上或注释中写下“触发条件是什么”、“需要读取什么数据”、“需要判断哪些分支”、“最终修改哪个 DOM 节点”。逻辑清晰之后,写代码只是填补具体的语法。
- 充分利用浏览器的开发者工具(DevTools):遇到界面没有反应时,第一步先按
F12检查 Console 控制台是否有红色报错;第二步在关键变量前后写上console.log('当前变量值:', val)查看数据是否符合预期;第三步在 Elements 面板中检查元素上的类名与行内样式是否发生改变。 - 尝试在原有案例上做“微扩展”:当上述案例能够完全独立敲出并正常运行后,可以尝试在现有代码上加一两个小功能。例如为计数器增加上限警示、为待办清单接入
localStorage本地缓存、给猜数字记录历史尝试列表。通过这种“小步快跑”的迭代,就能平滑迈入中大型前端项目的开发阶段。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。