欢迎来到编程的实战训练营!这一章我们将把前面学到的所有知识融会贯通,打造四个真实项目。就像从烹饪学校毕业前需要完成几道大菜一样,这些项目将成为你的"毕业作品集"。
13.1 待办事项应用 - 你的第一个"全栈"应用
项目目标:一个可以CRUD(增删改查)任务的待办清单
// 数据结构设计
let todos = [
{ id: 1, text: "学习JavaScript", completed: false },
{ id: 2, text: "买菜", completed: true }
];
// 核心功能实现
class TodoApp {
constructor() {
this.todos = JSON.parse(localStorage.getItem('todos')) || [];
this.render();
}
addTodo(text) {
this.todos.push({
id: Date.now(),
text,
completed: false
});
this._save();
}
toggleTodo(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) todo.completed = !todo.completed;
this._save();
}
deleteTodo(id) {
this.todos = this.todos.filter(t => t.id !== id);
this._save();
}
_save() {
localStorage.setItem('todos', JSON.stringify(this.todos));
this.render();
}
render() {
const list = document.getElementById('todo-list');
list.innerHTML = this.todos.map(todo => `
<li class="${todo.completed ? 'completed' : ''}">
<input type="checkbox" ${todo.completed ? 'checked' : ''}
onchange="app.toggleTodo(${todo.id})">
<span>${todo.text}</span>
<button onclick="app.deleteTodo(${todo.id})">删除</button>
</li>
`).join('');
}
}
const app = new TodoApp();
// 添加新任务
document.getElementById('add-btn').addEventListener('click', () => {
const input = document.getElementById('todo-input');
if (input.value.trim()) {
app.addTodo(input.value.trim());
input.value = '';
}
});
技术要点:
- 使用Class组织代码
- localStorage持久化数据
- 事件委托优化性能
- 模板字符串生成HTML
13.2 天气预报应用 - 对接真实API
项目目标:通过API获取并显示天气数据
// 使用Fetch API获取数据
async function getWeather(city) {
try {
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=YOUR_API_KEY&units=metric&lang=zh_cn`
);
const data = await response.json();
if (data.cod === 200) {
displayWeather(data);
} else {
throw new Error(data.message);
}
} catch (error) {
showError(error.message);
}
}
function displayWeather(data) {
const weatherDiv = document.getElementById('weather');
const { name, main, weather, wind } = data;
weatherDiv.innerHTML = `
<h2>${name}天气</h2>
<div class="weather-main">
<img src="http://openweathermap.org/img/wn/${weather[0].icon}@2x.png">
<span>${weather[0].description}</span>
</div>
<ul>
<li>温度: ${main.temp}°C</li>
<li>体感: ${main.feels_like}°C</li>
<li>湿度: ${main.humidity}%</li>
<li>风速: ${wind.speed}m/s</li>
</ul>
`;
}
// 获取用户位置
navigator.geolocation?.getCurrentPosition(async pos => {
const { latitude, longitude } = pos.coords;
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=YOUR_API_KEY&units=metric`
);
const data = await response.json();
displayWeather(data);
});
// 搜索城市
document.getElementById('search-btn').addEventListener('click', () => {
const city = document.getElementById('city-input').value.trim();
if (city) getWeather(city);
});
技术要点:
- 异步API调用
- Fetch API使用
- 地理位置API
- 错误处理
13.3 简易电商页面 - 前端+模拟后端
项目架构:
project/
├── index.html
├── style.css
├── script.js
└── db.json (模拟数据库)
// 模拟后端API
class MockAPI {
static async getProducts() {
const response = await fetch('db.json');
return await response.json();
}
static async addToCart(productId) {
// 模拟网络延迟
return new Promise(resolve => {
setTimeout(() => {
const cart = JSON.parse(localStorage.getItem('cart')) || [];
cart.push(productId);
localStorage.setItem('cart', JSON.stringify(cart));
resolve();
}, 500);
});
}
}
// 前端逻辑
class ECommerceApp {
async init() {
this.products = await MockAPI.getProducts();
this.renderProducts();
this.updateCartCount();
}
renderProducts() {
const container = document.getElementById('products');
container.innerHTML = this.products.map(product => `
<div class="product">
<img src="${product.image}">
<h3>${product.name}</h3>
<p>¥${product.price.toFixed(2)}</p>
<button onclick="app.addToCart(${product.id})">加入购物车</button>
</div>
`).join('');
}
async addToCart(productId) {
await MockAPI.addToCart(productId);
this.updateCartCount();
showToast('已添加到购物车');
}
updateCartCount() {
const cart = JSON.parse(localStorage.getItem('cart')) || [];
document.getElementById('cart-count').textContent = cart.length;
}
}
const app = new ECommerceApp();
app.init();
// 模拟Toast通知
function showToast(message) {
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.classList.add('show');
setTimeout(() => toast.remove(), 2000);
}, 100);
}
技术要点:
- 前后端分离架构
- 模拟API接口
- Promise处理异步
- 本地存储管理状态
13.4 数据可视化图表 - 使用Chart.js
项目目标:将数据转化为直观图表
<!-- 引入Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<canvas id="myChart" width="400" height="200"></canvas>
// 获取数据
async function fetchData() {
const response = await fetch('sales-data.json');
return await response.json();
}
// 创建图表
async function renderChart() {
const data = await fetchData();
const ctx = document.getElementById('myChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: data.map(item => item.month),
datasets: [{
label: '销售额 (万元)',
data: data.map(item => item.amount),
backgroundColor: [
'rgba(255, 99, 132, 0.7)',
'rgba(54, 162, 235, 0.7)',
'rgba(255, 206, 86, 0.7)',
'rgba(75, 192, 192, 0.7)',
'rgba(153, 102, 255, 0.7)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
},
plugins: {
title: {
display: true,
text: '2023年季度销售额'
}
}
}
});
}
// 添加交互功能
document.getElementById('chart-type').addEventListener('change', (e) => {
const type = e.target.value;
Chart.getChart('myChart').config.type = type;
Chart.getChart('myChart').update();
});
renderChart();
技术要点:
- 第三方库集成
- 数据映射与转换
- 图表配置与自定义
- 动态更新图表
本章总结:
- 待办事项:掌握状态管理与本地存储
- 天气预报:实战API调用与数据处理
- 电商页面:模拟前后端交互
- 数据可视化:学习图表库集成
升级挑战:
- 为待办事项添加分类标签功能
- 在天气应用中增加5天预报
- 为电商页面实现商品搜索和筛选
- 在图表中添加多数据集对比
开发者日志:
## Day 1
- 完成了TodoApp的基本CRUD功能
- 遇到问题:删除按钮有时不响应
- 解决方案:改用事件委托
## Day 2
- 成功接入OpenWeatherMap API
- 发现免费API有调用限制
- 优化:添加加载状态和错误提示
## Day 3
- 实现购物车本地存储
- 挑战:模拟网络延迟
- 解决:使用setTimeout包装Promise
## Day 4
- 集成Chart.js成功
- 学习到图表配置非常灵活
- 下一步:尝试动态切换图表类型
恭喜完成实战训练!这些项目将成为你简历中的亮点。记住,真正的学习发生在调试和优化过程中。接下来我们要探索性能优化的奥秘,让你的应用飞起来!