通过待办事项和天气应用项目巩固JavaScript基础知识

作者:像素猫时间:2026-10-30 18:43:46浏览:432

通过待办事项列表和天气查询应用两个经典实战项目,全面巩固JavaScript核心基础知识,包括DOM操作、事件模型、本地持久化与异步网络请求。

初学 JavaScript 时,很多学习者能够熟练背诵语法规则、数组方法或异步概念,但在面对空白代码编辑器时仍然不知从何下手。脱离实际场景的语法练习容易让人陷入纸上谈兵的困境。巩固语言基础最稳妥的路径,是用两个侧重点截然不同的小型项目形成互补:首先通过“待办事项应用(Todo App)”吃透浏览器原生 DOM 操作、事件监听、数据状态管理与本地持久化;随后通过“天气查询应用(Weather App)”彻底搞懂网络请求(Fetch API)、Promise 异步调用链以及接口异常处理。这两个项目分别覆盖了前端开发中最核心的“本地状态控制”与“远程异步交互”两大能力支柱。

通过待办事项和天气应用项目巩固JavaScript基础知识
图1:两个互补项目涵盖的 JavaScript 关键技术与能力结构分布

项目一:通过待办事项应用掌握 DOM 与本地存储

待办事项应用不仅是增删改查的雏形,更蕴含了现代前端框架底层的数据驱动思想。许多初学者容易犯的错误是直接操作 HTML 元素来保存数据(例如把是否完成记录在 class 样式上),导致业务状态和展示层强耦合。更规范的做法是用一个纯 JavaScript 数组作为“真实数据源”,所有用户操作都先修改数组,再统一重新渲染视图。

1. 数据模型与视图单向同步

在待办事项中,每个条目应该是一个拥有独立结构的对象:{ id: number, text: string, completed: boolean }。界面渲染函数只根据这个数组的内容生成 DOM,并同步到 localStorage 中。

通过待办事项和天气应用项目巩固JavaScript基础知识
图2:状态驱动视图的逻辑流转机制

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 => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
  }[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)。

通过待办事项和天气应用项目巩固JavaScript基础知识
图3:异步请求生命周期与防御性分支处理机制

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}&current_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);
  }
});

综合对比与知识体系总结

通过分别实现这两个项目,我们能建立起一套完整而清晰的前端开发思维模型。二者并非孤立的技术训练,而是对应着现代前端架构的两个半球:一侧是客户端本地的状态维护与无刷新视图交互,另一侧是客户端与服务器端的数据契约与通信链路。

通过待办事项和天气应用项目巩固JavaScript基础知识
图4:待办事项与天气查询项目的技术栈与考点能力对照表

完成这两个项目后,建议读者尝试将两者进行组合拓展:例如在待办事项中为每一项任务添加天气标签(当输入“去公园跑步”时自动调取当日气象),或者在天气应用中实现搜索历史持久化。通过在真实项目中不断打磨对 DOM 机制、数据流向和异步边界的处理能力,JavaScript 的基础知识才算真正内化为可随时调用的工程素养。

作者最新文章
相关文章
更多