author: 专注前端开发,分享JavaScript干货
title: JavaScript实战①|从零开发待办清单应用,巩固所学知识
update: 2026-04-28
tags: JavaScript,实战项目,待办清单,TodoApp,DOM操作,本地存储

作者:专注前端开发,分享JavaScript干货
更新时间:2026年4月
适合人群:学完本专栏基础内容,想通过实战巩固的开发者


前言:为什么做这个项目?

待办清单(Todo App)是前端入门的经典项目,涵盖:

  • DOM 操作(增删改查)
  • 事件处理
  • 本地存储(LocalStorage)
  • ES6+ 语法

做完这个项目,你的JS基础就扎实了。


一、项目功能规划

  1. ✅ 添加待办事项
  2. ✅ 标记完成/未完成
  3. ✅ 删除待办事项
  4. ✅ 筛选(全部/未完成/已完成)
  5. ✅ 本地存储(刷新不丢失)
  6. ✅ 统计待办数量

二、HTML 结构

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>待办清单</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 50px 20px;
        }
        .container {
            max-width: 500px;
            margin: 0 auto;
            background: white;
            border-radius: 16px;
            padding: 30px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
        }
        h1 {
            text-align: center;
            color: #333;
            margin-bottom: 30px;
        }
        .input-group {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
        }
        #todoInput {
            flex: 1;
            padding: 12px 16px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            font-size: 16px;
            outline: none;
            transition: border-color 0.3s;
        }
        #todoInput:focus {
            border-color: #667eea;
        }
        #addBtn {
            padding: 12px 24px;
            background: #667eea;
            color: white;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-size: 16px;
            transition: background 0.3s;
        }
        #addBtn:hover {
            background: #5a67d8;
        }
        .filters {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
            justify-content: center;
        }
        .filter-btn {
            padding: 8px 16px;
            border: 1px solid #e0e0e0;
            background: white;
            border-radius: 20px;
            cursor: pointer;
            transition: all 0.3s;
        }
        .filter-btn.active {
            background: #667eea;
            color: white;
            border-color: #667eea;
        }
        .todo-list {
            list-style: none;
        }
        .todo-item {
            display: flex;
            align-items: center;
            padding: 15px;
            border-bottom: 1px solid #f0f0f0;
            transition: background 0.3s;
        }
        .todo-item:hover {
            background: #f8f9fa;
        }
        .todo-item.completed .todo-text {
            text-decoration: line-through;
            color: #999;
        }
        .todo-checkbox {
            width: 20px;
            height: 20px;
            margin-right: 15px;
            cursor: pointer;
        }
        .todo-text {
            flex: 1;
            font-size: 16px;
        }
        .delete-btn {
            padding: 6px 12px;
            background: #ff4757;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
        }
        .delete-btn:hover {
            background: #ff3838;
        }
        .stats {
            text-align: center;
            margin-top: 20px;
            color: #666;
        }
        .empty-state {
            text-align: center;
            padding: 40px;
            color: #999;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>📝 待办清单</h1>
        
        <div class="input-group">
            <input type="text" id="todoInput" placeholder="添加新的待办事项...">
            <button id="addBtn">添加</button>
        </div>
        
        <div class="filters">
            <button class="filter-btn active" data-filter="all">全部</button>
            <button class="filter-btn" data-filter="active">未完成</button>
            <button class="filter-btn" data-filter="completed">已完成</button>
        </div>
        
        <ul class="todo-list" id="todoList"></ul>
        
        <div class="stats" id="stats"></div>
    </div>

    <script src="app.js"></script>
</body>
</html>

三、JavaScript 实现(app.js)

// 待办事项管理类
class TodoApp {
    constructor() {
        this.todos = JSON.parse(localStorage.getItem('todos')) || [];
        this.filter = 'all';
        
        this.todoInput = document.getElementById('todoInput');
        this.addBtn = document.getElementById('addBtn');
        this.todoList = document.getElementById('todoList');
        this.stats = document.getElementById('stats');
        this.filterBtns = document.querySelectorAll('.filter-btn');
        
        this.init();
    }
    
    init() {
        this.bindEvents();
        this.render();
    }
    
    bindEvents() {
        // 添加按钮点击
        this.addBtn.addEventListener('click', () => this.addTodo());
        
        // 回车键添加
        this.todoInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') this.addTodo();
        });
        
        // 筛选按钮
        this.filterBtns.forEach(btn => {
            btn.addEventListener('click', (e) => {
                this.filterBtns.forEach(b => b.classList.remove('active'));
                e.target.classList.add('active');
                this.filter = e.target.dataset.filter;
                this.render();
            });
        });
    }
    
    addTodo() {
        const text = this.todoInput.value.trim();
        if (!text) {
            alert('请输入待办事项');
            return;
        }
        
        const todo = {
            id: Date.now(),
            text: text,
            completed: false,
            createdAt: new Date().toISOString()
        };
        
        this.todos.unshift(todo);
        this.saveTodos();
        this.todoInput.value = '';
        this.render();
    }
    
    toggleTodo(id) {
        const todo = this.todos.find(t => t.id === id);
        if (todo) {
            todo.completed = !todo.completed;
            this.saveTodos();
            this.render();
        }
    }
    
    deleteTodo(id) {
        if (confirm('确定要删除这个待办事项吗?')) {
            this.todos = this.todos.filter(t => t.id !== id);
            this.saveTodos();
            this.render();
        }
    }
    
    getFilteredTodos() {
        switch (this.filter) {
            case 'active':
                return this.todos.filter(t => !t.completed);
            case 'completed':
                return this.todos.filter(t => t.completed);
            default:
                return this.todos;
        }
    }
    
    saveTodos() {
        localStorage.setItem('todos', JSON.stringify(this.todos));
    }
    
    render() {
        const filteredTodos = this.getFilteredTodos();
        
        if (filteredTodos.length === 0) {
            this.todoList.innerHTML = `
                <div class="empty-state">
                    <p>暂无待办事项</p>
                </div>
            `;
        } else {
            this.todoList.innerHTML = filteredTodos.map(todo => `
                <li class="todo-item ${todo.completed ? 'completed' : ''}">
                    <input type="checkbox" 
                           class="todo-checkbox" 
                           ${todo.completed ? 'checked' : ''}
                           onchange="app.toggleTodo(${todo.id})">
                    <span class="todo-text">${this.escapeHtml(todo.text)}</span>
                    <button class="delete-btn" onclick="app.deleteTodo(${todo.id})">删除</button>
                </li>
            `).join('');
        }
        
        // 更新统计
        const activeCount = this.todos.filter(t => !t.completed).length;
        const completedCount = this.todos.filter(t => t.completed).length;
        this.stats.textContent = `${this.todos.length} 项 | 未完成 ${activeCount} 项 | 已完成 ${completedCount}`;
    }
    
    escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }
}

// 初始化应用
const app = new TodoApp();

四、知识点回顾

知识点 应用
DOM 操作 getElementById, querySelector, innerHTML
事件监听 addEventListener
ES6 Class class TodoApp
数组方法 filter, find, map, forEach
本地存储 localStorage.setItem, localStorage.getItem
模板字符串 动态生成 HTML

五、扩展挑战

  1. 添加编辑功能:双击待办事项可以编辑
  2. 添加拖拽排序:可以拖动调整顺序
  3. 添加截止日期:显示距离截止还有多久
  4. 添加分类标签:工作、生活、学习等

六、课后作业

  1. 完成上述代码,运行并测试所有功能
  2. 尝试添加"清空已完成"按钮
  3. 给待办事项添加优先级(高/中/低)

有问题欢迎评论区留言,大家一起讨论!


标签:JavaScript | 实战项目 | 待办清单 | TodoApp | DOM操作 | 本地存储

更多推荐