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

在本指南里,当我说“应用”时,我指的是一个小型的网页应用:在浏览器中打开的单页,能响应你的点击和输入。不需要安装、不需要账户、也不需要复杂的设置——只是一个能在本地运行的小项目。
到最后,你会得到一个待办应用,它可以:
localStorage(这样关闭标签页不会丢失内容)它不会很完美或“企业级”,这正是目的:这是为初学者设计的项目,教你基础知识而不会丢给你太多工具。
你将一步一步构建这个应用,并掌握前端网页应用的核心部分:
保持简单。你只需要:
如果你能创建文件夹并编辑几个文件,就可以开始了。
在写任何代码之前,先决定什么才算“成功”。本教程只做一个小应用、一个明确的工作:帮你跟踪你想做的任务。
写一句话的目标,构建时随手可见:
“这个应用让我把任务添加到列表里,以免忘记。”
就是这样。如果你想加日历、提醒、标签或账户功能,把它们先放到以后再做。
快速列两份清单:
必须有(本项目):
localStorage)可选(今天不需要):到期日、优先级、分类、搜索、拖放、云同步。
把“必须有”控制在小范围内能帮助你真正完成项目。
这个应用可以是单页,包含:
明确一点,避免卡住:
决定后,就可以设置项目文件了。
在写任何代码之前,先为应用创建一个干净的小“家”。从一开始就整理好文件会让后面的步骤更顺利。
在电脑上新建一个文件夹,取名如 todo-app。这个文件夹会存放项目的所有内容。
在该文件夹中创建三个文件:
index.html(页面结构)styles.css(外观与布局)app.js(行为与交互)如果你的系统隐藏文件扩展名(比如“.html”),确认你真的是创建了 index.html,常见的初学者错误是最后变成了 index.html.txt。
在代码编辑器(VS Code、Sublime Text 等)中打开 todo-app 文件夹,然后在浏览器中打开 index.html。
此时页面可能是空白的——没关系,下一步我们会添加内容。
编辑文件时,浏览器不会自动更新(除非你使用自动刷新工具)。
基本流程是:
如果出现“没反应”的情况,先尝试刷新。
你也可以通过本地服务器打开页面,这能避免一些后续的奇怪问题(尤其是开始保存数据或加载文件时)。
适合初学者的选项:
python -m http.server
然后在浏览器中打开它打印的地址(通常是 http://localhost:8000)。
现在我们来做一个干净的骨架。这个 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 中频繁使用。
现在页面存在了,但可能看起来像一份普通文档。简单的 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 接上交互。
既然页面上有输入框、按钮和列表,我们就需要让它们“动”起来。目标很简单:当有人输入任务并点击 Add(或按 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 里的选择器完全对应(这是初学者常踩的坑)。
现在你可以添加任务了,接下来让任务可操作:可以标记为已完成,也可以删除它们。
不要只把任务当字符串保存,用对象更灵活。这样每个任务都有稳定的身份和“已完成”状态:
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 类来在视觉上标注已完成的任务。现在你的待办清单有个烦恼:刷新页面或关闭标签页后,所有内容都消失了。
原因是任务只存在于 JavaScript 的内存里,页面重载后内存会被清空。
localStorage 内置于浏览器。把它想象成一个可以按键(key)存放文本的小盒子。它适合初学者项目,因为不需要服务器或账号系统。
我们会把整个任务数组转为 JSON 文本保存,然后在页面打开时再读回来。
每当你添加、标记完成或删除任务时,都调用 saveTasks()。
const STORAGE_KEY = "todo.tasks";
function saveTasks(tasks) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
}
无论何处更新了 tasks 数组,紧接着执行:
saveTasks(tasks);
renderTasks(tasks);
页面加载时读取保存的值。如果没有保存过,就回退到空数组。
function loadTasks() {
const saved = localStorage.getItem(STORAGE_KEY);
return saved ? JSON.parse(saved) : [];
}
let tasks = loadTasks();
renderTasks(tasks);
就这样:你的应用现在能在刷新后记住任务。
提示:localStorage 只能存字符串,所以用 JSON.stringify() 把数组变成文本,用 JSON.parse() 在读取时再把文本变回数组。
测试听起来无聊,但它是把你的待办应用从“在我机器上能用”变成“在任何时候都能用”的最快方法。在每次小改动后做一次快速检查。
按下面顺序试一遍主要流程:
如果某一步出问题,先修复再做新功能。把问题堆在一起会让小应用变得难以维护。
边界情况是你没有特意设计但用户会做的操作:
常见的解决是阻止空任务:
const text = input.value.trim();
addButton.disabled = text === "";
(可以在每次 input 事件时执行,也可以在添加前再次检查。)
当点击没有反应,通常是因为:
app.js 找不到)当问题看起来随机时,打日志:
console.log("Adding:", text);
console.log("Current tasks:", tasks);
在浏览器的 Console 查看错误(红色文字)。问题解决后记得删掉这些日志,避免把项目搞得杂乱。
当一个待办应用“完成”的标准之一是:在手机上、用键盘以及使用无障碍工具(如屏幕朗读器)时都让人感觉舒适。
在小屏幕上,太小的按钮会让人很烦。确保可点击元素有足够空间:
在 CSS 中,增加 padding、font-size 与 gap 经常能带来明显改善。
屏幕朗读器需要清晰的控件名字:
<label>(这是最佳做法)。如果你不想显示它,可以用 CSS 将其视觉隐藏,但保留在 HTML 中。aria-label="Delete task",这样屏幕朗读器不会只读出“按钮”而没有上下文。这能帮助使用辅助工具的人理解每个控件的作用。
确保可以不靠鼠标也能操作应用:
<form> 可以让 Enter 自然工作)使用可读的字体大小(16px 是个好基线)和充足的颜色对比(深色文字配浅背景或反之)。不要只用颜色来表示“已完成”这一状态——加上线穿或其他明确样式更好。
功能都实现后,花 10–15 分钟把代码整理一下,这会让未来的修复更容易,也让你下次回来时更容易理解自己的项目。
保持小而可预测:
/index.html — 页面结构(输入、按钮、列表)/styles.css — 界面样式(间距、字体、已完成样式)/app.js — 行为(添加、切换完成、删除、保存/加载)/README.md — 给“未来的你”的快速说明如果你喜欢子文件夹,也可以:
/css/styles.css/js/app.js只要确保你的 <link> 和 <script> 路径一致。
几个快速改进:
taskInput、taskList、saveTasks()例如,以下一目了然:
renderTasks(tasks)addTask(text)toggleTask(id)deleteTask(id)你的 README.md 可以很简单:
index.html)至少在完成一个里程碑后压缩备份一次(例如“localStorage 生效”时)。如果想要版本历史,Git 很好用,但不是必须。哪怕一份备份也能防止误删造成的损失。
发布就是把应用文件(HTML、CSS、JavaScript)放到互联网上,让别人打开链接就能使用。由于这个待办应用是静态站点(在浏览器运行,不需要服务器),你可以免费托管在多个服务上。
大致步骤:
/)如果有多个文件,记得检查文件名与引用是否完全一致(例如 styles.css 与 style.css 的区别)。
如果你想最省事的“上传并使用”方式:
localStorage 生效)通过这些检查后,把链接发给朋友,让他们试一试——新鲜的眼睛常能发现问题。
你已经做出了一个可用的待办应用。如果想在不跳跃到巨型项目的情况下继续学习,这些升级既实用又能教到额外的模式。
给每个任务加一个“编辑”按钮。点击时把任务标签替换成一个小的输入框(预填当前文本),并显示“保存/取消”按钮。
提示:保持任务数据为带 id 和 text 的对象。编辑时就是按 id 找到任务、更新 text、重新渲染并保存。
在顶部加三个按钮:All、Active、Done。
把当前过滤器存在变量里,例如 currentFilter = 'all'。渲染时根据过滤器显示:
保持轻量:
YYYY-MM-DD),并显示在任务旁边哪怕只加一个字段,也能教你如何同时更新数据模型和界面。
当你准备好了,核心思路是:不再把任务保存到 localStorage,而是通过 fetch() 发送到 API(服务器)。服务器把它存到数据库,这样不同设备间就能同步任务。
如果你想在不重写全部代码的情况下尝试这个跳跃,一些生成/原型平台可以帮你快速试错并导出代码。
试试做一个笔记应用(带搜索)或一个习惯追踪器(每日打卡)。它们复用相同技能:列表渲染、编辑、保存与基础 UI 设计。