一步步构建简单待办应用(通俗易懂)
一份清晰的逐步指南,教你构建一个小型待办应用:规划功能、创建界面、添加逻辑、保存数据、测试并发布。

我们要做什么(以及你会学到什么)
在本指南里,当我说“应用”时,我指的是一个小型的网页应用:在浏览器中打开的单页,能响应你的点击和输入。不需要安装、不需要账户、也不需要复杂的设置——只是一个能在本地运行的小项目。
目标(完成后的功能)
到最后,你会得到一个待办应用,它可以:
- 添加任务:从输入框添加任务
- 标记任务为已完成(这样你可以看出哪些已完成)
- 删除任务:移除不需要的任务
- 在刷新后保留任务,使用
localStorage(这样关闭标签页不会丢失内容)
它不会很完美或“企业级”,这正是目的:这是为初学者设计的项目,教你基础知识而不会丢给你太多工具。
你会学到什么(通俗说明)
你将一步一步构建这个应用,并掌握前端网页应用的核心部分:
- HTML:创建页面结构(输入、按钮、列表)
- CSS:让界面可读且好用
- JavaScript:处理点击、创建列表项并更新页面
- localStorage:保存并加载任务,让它们持久化
- 基本的测试与调试(发现小错误并修复)
开始前你需要准备的东西
保持简单。你只需要:
- 一台电脑(Windows、Mac 或 Linux)
- 一个现代浏览器(Chrome、Firefox、Safari 或 Edge)
- 一个代码编辑器(VS Code 很受欢迎,但任何编辑器都可以)
如果你能创建文件夹并编辑几个文件,就可以开始了。
第 1 步:确定应用目标与功能
在写任何代码之前,先决定什么才算“成功”。本教程只做一个小应用、一个明确的工作:帮你跟踪你想做的任务。
选定应用的主要目标
写一句话的目标,构建时随手可见:
“这个应用让我把任务添加到列表里,以免忘记。”
就是这样。如果你想加日历、提醒、标签或账户功能,把它们先放到以后再做。
必须有 vs 可选功能
快速列两份清单:
必须有(本项目):
- 添加任务(输入文字,点击按钮)
- 在列表中查看任务
- 标记任务为已完成
- 删除任务
- 在刷新后保留任务(稍后我们会用
localStorage)
可选(今天不需要):到期日、优先级、分类、搜索、拖放、云同步。
把“必须有”控制在小范围内能帮助你真正完成项目。
用通俗的话描述界面
这个应用可以是单页,包含:
- 一个文本输入(“添加任务…”)和一个“添加”按钮
- 下方的任务列表
- 每个任务有一个“完成”切换和一个“删除”按钮
定义“已完成”是什么意思
明确一点,避免卡住:
- “已完成” = 任务看起来被划掉(或变淡),并且在被删除之前它仍保留在列表中。
决定后,就可以设置项目文件了。
第 2 步:搭建项目文件
在写任何代码之前,先为应用创建一个干净的小“家”。从一开始就整理好文件会让后面的步骤更顺利。
1)创建项目文件夹
在电脑上新建一个文件夹,取名如 todo-app。这个文件夹会存放项目的所有内容。
在该文件夹中创建三个文件:
index.html(页面结构)styles.css(外观与布局)app.js(行为与交互)
如果你的系统隐藏文件扩展名(比如“.html”),确认你真的是创建了 index.html,常见的初学者错误是最后变成了 index.html.txt。
2)在编辑器和浏览器中打开文件夹
在代码编辑器(VS Code、Sublime Text 等)中打开 todo-app 文件夹,然后在浏览器中打开 index.html。
此时页面可能是空白的——没关系,下一步我们会添加内容。
3)如何查看修改效果
编辑文件时,浏览器不会自动更新(除非你使用自动刷新工具)。
基本流程是:
- 在编辑器保存文件
- 切到浏览器页签
- 刷新页面查看更新
如果出现“没反应”的情况,先尝试刷新。
可选:使用本地服务器(推荐但非必须)
你也可以通过本地服务器打开页面,这能避免一些后续的奇怪问题(尤其是开始保存数据或加载文件时)。
适合初学者的选项:
- 在 VS Code 安装 Live Server,然后点击 “Go Live”
- 如果电脑上已安装 Python,在项目文件夹中运行:
python -m http.server
然后在浏览器中打开它打印的地址(通常是 http://localhost:8000)。
第 3 步:构建页面结构(HTML)
现在我们来做一个干净的骨架。这个 HTML 不会让页面变得“可交互”(交互是下一步的事),但它会为你的 JavaScript 提供清晰的读写位置。
我们需要的最小布局
包括:
- 一个标题,让页面一目了然
- 一个输入框用于任务文本
- 一个添加按钮用以提交任务
- 一个列表区域,用来显示新增任务
使用简单且可读的名称。良好的 id/class 会让后面 JavaScript 通过名称获取元素时更方便。
将下面内容粘到你的 index.html 中
\u003c!doctype html\u003e
\u003chtml lang=\"en\"\u003e
\u003chead\u003e
\u003cmeta charset=\"utf-8\" /\u003e
\u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /\u003e
\u003ctitle\u003eTo‑Do App\u003c/title\u003e
\u003clink rel=\"stylesheet\" href=\"styles.css\" /\u003e
\u003c/head\u003e
\u003cbody\u003e
\u003cmain class=\"app\" id=\"app\"\u003e
\u003ch1 class=\"app__title\" id=\"appTitle\"\u003eMy To‑Do List\u003c/h1\u003e
\u003cform class=\"task-form\" id=\"taskForm\"\u003e
\u003clabel class=\"task-form__label\" for=\"taskInput\"\u003eNew task\u003c/label\u003e
\u003cdiv class=\"task-form__row\"\u003e
\u003cinput
id=\"taskInput\"
class=\"task-form__input\"
type=\"text\"
placeholder=\"e.g., Buy milk\"
autocomplete=\"off\"
/\u003e
\u003cbutton id=\"addButton\" class=\"task-form__button\" type=\"submit\"\u003e
Add
\u003c/button\u003e
\u003c/div\u003e
\u003c/form\u003e
\u003cul class=\"task-list\" id=\"taskList\" aria-label=\"Task list\"\u003e\u003c/ul\u003e
\u003c/main\u003e
\u003cscript src=\"app.js\"\u003e\u003c/script\u003e
\u003c/body\u003e
\u003c/html\u003e
结构就这些。注意我们用了 id="taskInput" 和 id="taskList"——这两个元素将会在 JavaScript 中频繁使用。
第 4 步:做点样式让界面更清爽(CSS)
现在页面存在了,但可能看起来像一份普通文档。简单的 CSS 会让它更好用:间距更清晰、文字可读、按钮更像可点击的元素。
从一个居中的容器开始
居中的框能让应用更集中,防止内容在宽屏上拉得太开。
/* Basic page setup */
body {
font-family: Arial, sans-serif;
background: #f6f7fb;
margin: 0;
padding: 24px;
}
/* Centered app container */
.container {
max-width: 520px;
margin: 0 auto;
background: #ffffff;
padding: 16px;
border-radius: 10px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
}
让任务便于扫视
每个任务应该看起来像独立的一行,间距舒适。
ul { list-style: none; padding: 0; margin: 16px 0 0; }
li {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 12px;
border: 1px solid #e7e7ee;
border-radius: 8px;
margin-bottom: 10px;
}
.task-text { flex: 1; }
添加清晰的“已完成”样式
当任务完成时,应有明显的视觉变化,便于一眼识别。
.done .task-text {
text-decoration: line-through;
color: #777;
opacity: 0.85;
}
按钮风格一致
保持按钮尺寸与样式一致,会让它们看起来像同一个应用的一部分。
button {
border: none;
border-radius: 8px;
padding: 8px 10px;
cursor: pointer;
}
button:hover { filter: brightness(0.95); }
这就足够让 UI 干净友好了——不需要花哨技巧。接下来我们用 JavaScript 接上交互。
第 5 步:添加任务(JavaScript 基础)
既然页面上有输入框、按钮和列表,我们就需要让它们“动”起来。目标很简单:当有人输入任务并点击 Add(或按 Enter)时,列表里出现新项。
绑定按钮和 Enter 键
在你的 JavaScript 文件中,先获取需要的元素,然后监听两种动作:按钮点击和输入框内的 Enter。
const taskInput = document.querySelector('#taskInput');
const addBtn = document.querySelector('#addBtn');
const taskList = document.querySelector('#taskList');
function addTask() {
const text = taskInput.value.trim();
// Block empty tasks (including ones that are just spaces)
if (text === '') return;
const li = document.createElement('li');
li.textContent = text;
taskList.appendChild(li);
// Clear the input and put the cursor back
taskInput.value = '';
taskInput.focus();
}
addBtn.addEventListener('click', addTask);
taskInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
addTask();
}
});
这段代码做了什么(通俗说明)
- 读取输入框里的文本并用
trim()去掉首尾多余空格。 - 如果是空文本就阻止添加,避免出现空项。
- 创建一个新的
<li>,设置它的文本并加入列表中。 - 清空输入框并把焦点放回输入框,方便快速继续输入下一个任务。
如果没有反应,先检查 HTML 中的元素 id 是否与 JavaScript 里的选择器完全对应(这是初学者常踩的坑)。
第 6 步:标记完成与删除任务
现在你可以添加任务了,接下来让任务可操作:可以标记为已完成,也可以删除它们。
将任务存成简单的数据结构
不要只把任务当字符串保存,用对象更灵活。这样每个任务都有稳定的身份和“已完成”状态:
text:任务内容done:true或falseid:唯一编号,用来定位或删除特定任务
示例:
let tasks = [
{ id: 1, text: "Buy milk", done: false },
{ id: 2, text: "Email Sam", done: true }
];
添加“完成”控件和删除按钮
渲染每个任务时,包含一个复选框或“完成”按钮,以及一个“删除”按钮。
事件监听器(event listener)就是用来响应点击的。你把监听器挂到按钮(或整个列表容器)上,当用户点击时,对应的代码就会运行。
对初学者友好的做法是使用事件委托:在任务列表容器上放一个点击监听器,然后根据被点击的元素判断操作。
function toggleDone(id) {
tasks = tasks.map(t => t.id === id ? { ...t, done: !t.done } : t);
renderTasks();
}
function deleteTask(id) {
tasks = tasks.filter(t => t.id !== id);
renderTasks();
}
document.querySelector("#taskList").addEventListener("click", (e) => {
const id = Number(e.target.dataset.id);
if (e.target.matches(".toggle")) toggleDone(id);
if (e.target.matches(".delete")) deleteTask(id);
});
在你的 renderTasks() 函数中:
- 给每个按钮加上
data-id="${task.id}"。 - 用
.done类来在视觉上标注已完成的任务。
第 7 步:保存任务以便持久化(localStorage)
现在你的待办清单有个烦恼:刷新页面或关闭标签页后,所有内容都消失了。
原因是任务只存在于 JavaScript 的内存里,页面重载后内存会被清空。
localStorage = 浏览器内一个小“储物箱”
localStorage 内置于浏览器。把它想象成一个可以按键(key)存放文本的小盒子。它适合初学者项目,因为不需要服务器或账号系统。
我们会把整个任务数组转为 JSON 文本保存,然后在页面打开时再读回来。
1)每次变更后保存
每当你添加、标记完成或删除任务时,都调用 saveTasks()。
const STORAGE_KEY = "todo.tasks";
function saveTasks(tasks) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
}
无论何处更新了 tasks 数组,紧接着执行:
saveTasks(tasks);
renderTasks(tasks);
2)页面启动时加载并渲染
页面加载时读取保存的值。如果没有保存过,就回退到空数组。
function loadTasks() {
const saved = localStorage.getItem(STORAGE_KEY);
return saved ? JSON.parse(saved) : [];
}
let tasks = loadTasks();
renderTasks(tasks);
就这样:你的应用现在能在刷新后记住任务。
提示:localStorage 只能存字符串,所以用 JSON.stringify() 把数组变成文本,用 JSON.parse() 在读取时再把文本变回数组。
第 8 步:测试并修复常见错误
测试听起来无聊,但它是把你的待办应用从“在我机器上能用”变成“在任何时候都能用”的最快方法。在每次小改动后做一次快速检查。
快速测试流程(60 秒)
按下面顺序试一遍主要流程:
- 添加一个任务
- 标记为完成
- 删除它
- 刷新页面(确认 UI 行为仍正确)
如果某一步出问题,先修复再做新功能。把问题堆在一起会让小应用变得难以维护。
常见会搞坏初学者应用的边界情况
边界情况是你没有特意设计但用户会做的操作:
- 很长的文本(会不会溢出或把按钮挤走?)
- 重复任务(你是否删除了正确的那个?)
- 空输入(会不会添加空任务?)
常见的解决是阻止空任务:
const text = input.value.trim();
addButton.disabled = text === "";
(可以在每次 input 事件时执行,也可以在添加前再次检查。)
快速排查“没有反应”的清单
当点击没有反应,通常是因为:
- 选择器不匹配(HTML 的 id/class 与 JS 不一致)
- 脚本未加载(文件名/路径错误,或
app.js找不到) - 有 JavaScript 错误阻止了后面的代码执行
用控制台查看实际发生了什么
当问题看起来随机时,打日志:
console.log("Adding:", text);
console.log("Current tasks:", tasks);
在浏览器的 Console 查看错误(红色文字)。问题解决后记得删掉这些日志,避免把项目搞得杂乱。
第 9 步:提升可用性(无障碍与移动端)
当一个待办应用“完成”的标准之一是:在手机上、用键盘以及使用无障碍工具(如屏幕朗读器)时都让人感觉舒适。
让移动端易于点击
在小屏幕上,太小的按钮会让人很烦。确保可点击元素有足够空间:
- 按钮使用更大的内边距(高度目标约 44px)
- 完成 与 删除 按钮之间留出间距,避免误触
- 在移动端让输入框占满宽度,打字更自然
在 CSS 中,增加 padding、font-size 与 gap 经常能带来明显改善。
添加标签与 ARIA(是什么与为什么)
屏幕朗读器需要清晰的控件名字:
- 文本输入应有真实的
<label>(这是最佳做法)。如果你不想显示它,可以用 CSS 将其视觉隐藏,但保留在 HTML 中。 - 仅靠图标的按钮(比如垃圾桶图标)应包含
aria-label="Delete task",这样屏幕朗读器不会只读出“按钮”而没有上下文。
这能帮助使用辅助工具的人理解每个控件的作用。
键盘支持:Tab + Enter
确保可以不靠鼠标也能操作应用:
- 使用 Tab 能切到输入框和按钮
- 在输入框按 Enter 应该能添加任务(使用
<form>可以让 Enter 自然工作) - 添加任务后考虑把焦点返回到输入框,方便连续添加
可读性:对比度与字体大小
使用可读的字体大小(16px 是个好基线)和充足的颜色对比(深色文字配浅背景或反之)。不要只用颜色来表示“已完成”这一状态——加上线穿或其他明确样式更好。
第 10 步:整理与优化代码
功能都实现后,花 10–15 分钟把代码整理一下,这会让未来的修复更容易,也让你下次回来时更容易理解自己的项目。
简单的文件结构
保持小而可预测:
/index.html— 页面结构(输入、按钮、列表)/styles.css— 界面样式(间距、字体、已完成样式)/app.js— 行为(添加、切换完成、删除、保存/加载)/README.md— 给“未来的你”的快速说明
如果你喜欢子文件夹,也可以:
/css/styles.css/js/app.js
只要确保你的 <link> 和 <script> 路径一致。
让 JavaScript 更易读
几个快速改进:
- 用清晰的名字:
taskInput、taskList、saveTasks() - 保持函数短小:每个函数只做一件事
- 仅在代码不明显时添加简短注释(避免每行都注释)
例如,以下一目了然:
renderTasks(tasks)addTask(text)toggleTask(id)deleteTask(id)
添加简短的 README
你的 README.md 可以很简单:
- 功能说明(一句:一个会保存任务的待办列表)
- 如何运行(在浏览器中打开
index.html) - 可选:已知的限制
备份你的项目
至少在完成一个里程碑后压缩备份一次(例如“localStorage 生效”时)。如果想要版本历史,Git 很好用,但不是必须。哪怕一份备份也能防止误删造成的损失。
第 11 步:发布并分享你的应用
发布就是把应用文件(HTML、CSS、JavaScript)放到互联网上,让别人打开链接就能使用。由于这个待办应用是静态站点(在浏览器运行,不需要服务器),你可以免费托管在多个服务上。
选项 1:GitHub Pages(适合静态站点)
大致步骤:
- 注册 GitHub(如果还没有)
- 新建仓库并上传文件(或用 Git 推送)
- 在仓库的 Settings → Pages 中设置
- 选择要发布的分支(通常是 main)和文件夹(通常是根目录
/) - 保存后,GitHub 会给你一个公开的 URL
如果有多个文件,记得检查文件名与引用是否完全一致(例如 styles.css 与 style.css 的区别)。
选项 2:Netlify 或 Vercel(拖拽式更简单)
如果你想最省事的“上传并使用”方式:
- 在 Netlify 或 Vercel 注册账号
- 找到 Deploy / Add New Project
- 把项目文件夹拖放上去(或连接 GitHub 仓库)
- 等待上传/构建完成
- 打开生成的 URL 并测试
分享前的快速检查清单
- 刷新页面:任务仍然保留(
localStorage生效) - 添加、完成与删除任务:没有坏掉的按钮
- 在手机上打开(或缩小浏览器窗口):没有溢出的内容
通过这些检查后,把链接发给朋友,让他们试一试——新鲜的眼睛常能发现问题。
后续可尝试的简单升级
你已经做出了一个可用的待办应用。如果想在不跳跃到巨型项目的情况下继续学习,这些升级既实用又能教到额外的模式。
1)编辑任务文本
给每个任务加一个“编辑”按钮。点击时把任务标签替换成一个小的输入框(预填当前文本),并显示“保存/取消”按钮。
提示:保持任务数据为带 id 和 text 的对象。编辑时就是按 id 找到任务、更新 text、重新渲染并保存。
2)筛选:全部 / 未完成 / 已完成
在顶部加三个按钮:All、Active、Done。
把当前过滤器存在变量里,例如 currentFilter = 'all'。渲染时根据过滤器显示:
- All:所有任务
- Active:未完成任务
- Done:已完成任务
3)到期日或简单分类
保持轻量:
- 加一个可选的到期日(格式
YYYY-MM-DD),并显示在任务旁边 - 或用下拉菜单加一个“Work / Home / School”之类的分类
哪怕只加一个字段,也能教你如何同时更新数据模型和界面。
4)进阶:从 localStorage 迁移到简单后端
当你准备好了,核心思路是:不再把任务保存到 localStorage,而是通过 fetch() 发送到 API(服务器)。服务器把它存到数据库,这样不同设备间就能同步任务。
如果你想在不重写全部代码的情况下尝试这个跳跃,一些生成/原型平台可以帮你快速试错并导出代码。
5)下一个适合初学者的项目
试试做一个笔记应用(带搜索)或一个习惯追踪器(每日打卡)。它们复用相同技能:列表渲染、编辑、保存与基础 UI 设计。
常见问题
构建这个待办应用需要什么?
你只需要一台电脑、现代浏览器和代码编辑器。创建一个文件夹,包含 index.html、styles.css 和 app.js,然后在浏览器中打开 HTML 文件。
适合初学者的待办应用应包含哪些功能?
先实现四个操作:添加任务、在列表中查看任务、将任务标记为完成,以及删除任务。截止日期、分类和账户等附加功能可以以后再加。
项目中的每个文件分别做什么?
HTML 用于创建输入框、按钮和任务列表。CSS 控制间距和外观。JavaScript 读取用户操作、更新列表并保存任务数据。
如何防止添加空白任务?
使用包含文本输入框和提交按钮的表单。JavaScript 应去除输入值两端的空白,拒绝空结果,创建列表项,然后清空输入框并让它重新获得焦点。
为什么任务应使用对象而不是纯文本?
使用包含 id、text 和 done 值的任务对象。即使两个任务的文本相同,ID 也能让代码更新或删除准确的那个任务。
如何将任务标记为已完成?
为每个任务添加复选框或“完成”按钮。用户点击后,修改该任务的 done 值,并添加一个 CSS 类来应用删除线或淡化样式。
应用如何在刷新后记住任务?
使用 JSON.stringify() 将任务数组存储在 localStorage 的一个名称下,并在页面启动时用 JSON.parse() 读取。添加、完成、编辑或删除任务后,都要再次保存。
为什么我的“添加”按钮没有任何反应?
先刷新页面,并打开浏览器控制台查看红色错误信息。然后检查 HTML ID 是否与 JavaScript 选择器匹配,以及脚本文件名是否与 index.html 中的路径一致。
如何让应用在手机上和使用键盘时更易用?
为任务输入框使用真正的标签,确保控件足够大,方便点击,并用 Tab 和 Enter 测试应用。已完成的任务不应只靠颜色区分,还应使用删除线和清晰的文本等提示。
发布待办应用前应测试什么?
测试添加任务、将其标记为完成、删除任务以及刷新页面。分享应用前,还要尝试空输入、较长的任务名称和重复的任务文本。