通过待办事项和天气应用项目巩固JavaScript基础知识
通过待办事项列表和天气查询应用两个经典实战项目,全面巩固JavaScript核心基础知识,包括DOM操作、事件模型、本地持久化与异步网络请求。
初学 JavaScript 时,很多学习者能够熟练背诵语法规则、数组方法或异步概念,但在面对空白代码编辑器时仍然不知从何下手。脱离实际场景的语法练习容易让人陷入纸上谈兵的困境。巩固语言基础最稳妥的路径,是用两个侧重点截然不同的小型项目形成互补:首先通过“待办事项应用(Todo App)”吃透浏览器原生 DOM 操作、事件监听、数据状态管理与本地持久化;随后通过“天气查询应用(Weather App)”彻底搞懂网络请求(Fetch API)、Promise 异步调用链以及接口异常处理。这两个项目分别覆盖了前端开发中最核心的“本地状态控制”与“远程异步交互”两大能力支柱。
项目一:通过待办事项应用掌握 DOM 与本地存储
待办事项应用不仅是增删改查的雏形,更蕴含了现代前端框架底层的数据驱动思想。许多初学者容易犯的错误是直接操作 HTML 元素来保存数据(例如把是否完成记录在 class 样式上),导致业务状态和展示层强耦合。更规范的做法是用一个纯 JavaScript 数组作为“真实数据源”,所有用户操作都先修改数组,再统一重新渲染视图。
1. 数据模型与视图单向同步
在待办事项中,每个条目应该是一个拥有独立结构的对象:{ id: number, text: string, completed: boolean }。界面渲染函数只根据这个数组的内容生成 DOM,并同步到 localStorage 中。
2. 核心代码与实现细节
在这个项目中,我们重点考察事件委托(Event Delegation)的运用。如果不使用事件委托,每次新增一个任务项,都需要给新增的删除按钮单独绑定 addEventListener,这不仅消耗内存,还容易在动态移除节点时产生未解绑监听器的隐患。利用事件冒泡机制,只需将监听器挂载在父级 <ul> 容器上即可:
// 1. 初始化数据:优先从 LocalStorage 中读取,若无则为空数组
let todos = JSON.parse(localStorage.getItem('my_todos')) || [];
const todoForm = document.querySelector('#todo-form');
const todoInput = document.querySelector('#todo-input');
const todoList = document.querySelector('#todo-list');
// 2. 数据持久化与界面重绘
function saveAndRender() {
localStorage.setItem('my_todos', JSON.stringify(todos));
render();
}
function render() {
todoList.innerHTML = '';
todos.forEach(todo => {
const li = document.createElement('li');
li.dataset.id = todo.id;
li.className = todo.completed ? 'completed' : '';
li.innerHTML = `
<input type="checkbox" class="toggle-check" ${todo.completed ? 'checked' : ''}>
<span class="todo-text">${escapeHTML(todo.text)}</span>
<button class="delete-btn">删除</button>
`;
todoList.appendChild(li);
});
}
// 辅助函数:防止用户输入 HTML 实体引起注入
function escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
}[m]));
}
// 3. 添加新任务
todoForm.addEventListener('submit', (e) => {
e.preventDefault();
const text = todoInput.value.trim();
if (!text) return;
todos.push({
id: Date.now(),
text: text,
completed: false
});
todoInput.value = '';
saveAndRender();
});
// 4. 事件委托:统一管理完成状态切换与删除操作
todoList.addEventListener('click', (e) => {
const target = e.target;
const parentLi = target.closest('li');
if (!parentLi) return;
const id = Number(parentLi.dataset.id);
if (target.classList.contains('delete-btn')) {
todos = todos.filter(t => t.id !== id);
saveAndRender();
} else if (target.classList.contains('toggle-check')) {
const item = todos.find(t => t.id === id);
if (item) {
item.completed = target.checked;
saveAndRender();
}
}
});
// 初始渲染
render();
localStorage 只能保存字符串,因此存取时必须成对使用 JSON.stringify 与 JSON.parse。此外,动态拼接文本到 innerHTML 时,切勿直接插入未转义的用户输入,必须经过转义处理,以防跨站脚本漏洞。
项目二:通过天气应用吃透异步编程与 Fetch API
如果说待办事项巩固了同步数据流与本地 DOM 渲染,那么天气应用则是攻克异步编程的最佳练兵场。在浏览器端,所有的网络通信都是异步的。从前期的回调地狱(Callback Hell)到后期的 Promise 链式调用,再到如今广泛采用的 async/await,这一演进过程是每个 JavaScript 开发者必须跨越的认知门槛。
1. 网络请求时序与状态控制
开发天气查询功能时,界面的响应并不能立即获得结果。一个规范的异步交互必须覆盖四个状态阶段:空闲(Idle)、加载中(Loading)、成功(Success)和失败(Error)。
2. 异步请求实现与关键考点
很多初学者在使用 fetch 时,容易误以为只要返回 404 或 500 状态码,fetch 就会自动跳转到 catch 块。实际上,fetch 只有在遇到网络断开或跨域被拦截等底层故障时才会 reject;如果服务端成功返回了响应(即使状态码是 404 或 500),Promise 依然处于 resolve 状态。因此必须手动通过 response.ok 进行校验:
// 获取 DOM 元素
const searchBtn = document.querySelector('#search-btn');
const cityInput = document.querySelector('#city-input');
const weatherCard = document.querySelector('#weather-card');
const statusMsg = document.querySelector('#status-msg');
async function fetchWeatherData(city) {
// 免费开源天气 API (以 open-meteo 配合地理编码为例)
const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1&language=zh&format=json`;
// 1. 设置交互为加载中状态
searchBtn.disabled = true;
statusMsg.textContent = '正在获取气象信息...';
weatherCard.style.display = 'none';
try {
const geoRes = await fetch(geocodingUrl);
if (!geoRes.ok) {
throw new Error(`地理编码请求失败: ${geoRes.status}`);
}
const geoData = await geoRes.json();
if (!geoData.results || geoData.results.length === 0) {
throw new Error('未找到该城市,请检查城市名称拼写');
}
const { latitude, longitude, name } = geoData.results[0];
// 2. 发起次级气象数据请求
const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t_weather=true`;
const weatherRes = await fetch(weatherUrl);
if (!weatherRes.ok) {
throw new Error(`天气接口返回异常: ${weatherRes.status}`);
}
const weatherData = await weatherRes.json();
// 3. 渲染视图
displayWeather(name, weatherData.current_weather);
statusMsg.textContent = '';
} catch (err) {
// 4. 统一捕获异常并给用户明确反馈
statusMsg.textContent = `查询出错: ${err.message}`;
} finally {
// 无论成败,恢复按钮可用状态
searchBtn.disabled = false;
}
}
function displayWeather(cityName, current) {
weatherCard.innerHTML = `
<h3>${cityName}</h3>
<p>当前温度: <strong>${current.temperature}°C</strong></p>
<p>风速: ${current.windspeed} km/h</p>
<p>观测时间: ${current.time}</p>
`;
weatherCard.style.display = 'block';
}
searchBtn.addEventListener('click', () => {
const city = cityInput.value.trim();
if (city) {
fetchWeatherData(city);
}
});
综合对比与知识体系总结
通过分别实现这两个项目,我们能建立起一套完整而清晰的前端开发思维模型。二者并非孤立的技术训练,而是对应着现代前端架构的两个半球:一侧是客户端本地的状态维护与无刷新视图交互,另一侧是客户端与服务器端的数据契约与通信链路。
完成这两个项目后,建议读者尝试将两者进行组合拓展:例如在待办事项中为每一项任务添加天气标签(当输入“去公园跑步”时自动调取当日气象),或者在天气应用中实现搜索历史持久化。通过在真实项目中不断打磨对 DOM 机制、数据流向和异步边界的处理能力,JavaScript 的基础知识才算真正内化为可随时调用的工程素养。
vtk.js 渲染窗口黑屏或无内容显示的常见原因及排查
2026-11-05 17:30:30
js-cookie 安装与基础用法:set、get、remove 详解
2026-11-05 16:35:06
适合JavaScript初学者的实战项目:制作简易待办事项列表
2026-11-05 16:17:07
JS打开新标签页时怎么通过URL传递参数
2026-11-05 12:21:08
sigma.js 自定义节点样式:设置颜色、大小及标签显示的技巧
2026-11-05 09:55:00








