引言

HTML5作为现代Web开发的基石,已经远远超越了简单的标记语言。它提供了丰富的API和功能,使得开发者能够构建交互性强、性能优越的Web应用。本文将从零基础开始,系统性地解析HTML5前端开发的核心技能,并通过实战项目帮助你巩固所学知识。

第一部分:HTML5基础入门

1.1 HTML5文档结构

HTML5的文档结构比之前的版本更加简洁。一个标准的HTML5文档如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>我的第一个HTML5页面</title>
</head>
<body>
    <h1>欢迎来到HTML5世界</h1>
    <p>这是一个段落文本。</p>
</body>
</html>

关键点解析:

  • <!DOCTYPE html>:声明文档类型为HTML5
  • <meta charset="UTF-8">:指定字符编码为UTF-8
  • <meta name="viewport">:移动端适配的关键设置
  • <html lang="zh-CN">:指定页面语言为中文

1.2 HTML5语义化标签

HTML5引入了新的语义化标签,使代码结构更清晰:

<header>
    <nav>
        <ul>
            <li><a href="#">首页</a></li>
            <li><a href="#">产品</a></li>
            <li><a href="#">关于我们</a></li>
        </ul>
    </nav>
</header>

<main>
    <article>
        <h2>文章标题</h2>
        <p>文章内容...</p>
    </article>
    <aside>
        <h3>相关链接</h3>
        <ul>
            <li><a href="#">链接1</a></li>
        </ul>
    </aside>
</main>

<footer>
    <p>&copy; 2023 我的网站</p>
</footer>

语义化标签的优势:

  • 提升SEO效果
  • 便于屏幕阅读器解析
  • 代码结构更清晰
  • 便于维护和协作

1.3 表单增强

HTML5对表单进行了大量增强:

<form id="userForm">
    <!-- 新的输入类型 -->
    <div>
        <label for="email">邮箱:</label>
        <input type="email" id="email" required placeholder="请输入邮箱">
    </div>
    
    <div>
        <label for="phone">电话:</label>
        <input type="tel" id="phone" pattern="[0-9]{11}" placeholder="请输入11位手机号">
    </div>
    
    <div>
        <label for="date">日期:</label>
        <input type="date" id="date">
    </div>
    
    <div>
        <label for="range">音量:</label>
        <input type="range" id="range" min="0" max="100" value="50">
    </div>
    
    <div>
        <label for="color">颜色:</label>
        <input type="color" id="color" value="#ff0000">
    </div>
    
    <!-- 新的表单属性 -->
    <div>
        <label for="username">用户名:</label>
        <input type="text" id="username" required minlength="3" maxlength="20">
    </div>
    
    <div>
        <label for="password">密码:</label>
        <input type="password" id="password" required pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}">
    </div>
    
    <button type="submit">提交</button>
</form>

HTML5表单新特性:

  • 新的输入类型:email、tel、date、range、color等
  • 新的表单属性:required、pattern、min、max、minlength、maxlength等
  • 表单验证API:checkValidity()validity对象等

第二部分:CSS3核心技能

2.1 CSS3选择器

CSS3提供了更强大的选择器:

/* 属性选择器 */
input[type="text"] {
    border: 1px solid #ccc;
}

/* 结构伪类选择器 */
ul li:first-child {
    font-weight: bold;
}

ul li:last-child {
    border-bottom: none;
}

ul li:nth-child(odd) {
    background-color: #f5f5f5;
}

/* 状态伪类选择器 */
a:hover {
    color: #ff6600;
}

input:focus {
    outline: 2px solid #0066cc;
}

/* 组合选择器 */
.nav > ul > li {
    display: inline-block;
}

/* 伪元素选择器 */
p::first-letter {
    font-size: 2em;
    font-weight: bold;
}

p::first-line {
    color: #333;
}

2.2 CSS3布局技术

Flexbox布局

.container {
    display: flex;
    flex-direction: row; /* row | row-reverse | column | column-reverse */
    justify-content: space-between; /* 主轴对齐 */
    align-items: center; /* 交叉轴对齐 */
    flex-wrap: wrap; /* 换行 */
    gap: 20px; /* 项目间距 */
}

.item {
    flex: 1; /* 等分剩余空间 */
    min-width: 200px;
}

/* 垂直居中示例 */
.flex-center {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
}

Grid布局

.grid-container {
    display: grid;
    grid-template-columns: repeat(3, 1fr); /* 三列等宽 */
    grid-template-rows: auto 1fr auto; /* 三行:自适应、剩余空间、自适应 */
    gap: 20px;
    padding: 20px;
}

/* 复杂布局示例 */
.dashboard {
    display: grid;
    grid-template-areas:
        "header header header"
        "sidebar main main"
        "footer footer footer";
    grid-template-columns: 200px 1fr 1fr;
    grid-template-rows: 60px 1fr 60px;
    height: 100vh;
}

.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }

2.3 CSS3动画与过渡

/* 过渡效果 */
.button {
    background-color: #0066cc;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    transition: all 0.3s ease;
}

.button:hover {
    background-color: #004c99;
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}

/* 关键帧动画 */
@keyframes slideIn {
    0% {
        transform: translateX(-100%);
        opacity: 0;
    }
    100% {
        transform: translateX(0);
        opacity: 1;
    }
}

@keyframes pulse {
    0%, 100% {
        transform: scale(1);
    }
    50% {
        transform: scale(1.05);
    }
}

.animated-element {
    animation: slideIn 0.5s ease-out;
}

.pulse {
    animation: pulse 2s infinite;
}

/* 3D变换 */
.card-3d {
    perspective: 1000px;
    width: 300px;
    height: 200px;
    margin: 50px auto;
}

.card-inner {
    width: 100%;
    height: 100%;
    transition: transform 0.6s;
    transform-style: preserve-3d;
    position: relative;
}

.card-3d:hover .card-inner {
    transform: rotateY(180deg);
}

.card-front, .card-back {
    position: absolute;
    width: 100%;
    height: 100%;
    backface-visibility: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 24px;
    border-radius: 10px;
}

.card-front {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
}

.card-back {
    background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
    color: white;
    transform: rotateY(180deg);
}

第三部分:JavaScript核心技能

3.1 ES6+新特性

// 1. let和const
let name = "张三";
const PI = 3.14159;

// 2. 箭头函数
const add = (a, b) => a + b;
const square = x => x * x;

// 3. 模板字符串
const user = {
    name: "李四",
    age: 25
};
const message = `用户${user.name},今年${user.age}岁`;

// 4. 解构赋值
const person = {
    name: "王五",
    age: 30,
    city: "北京"
};

const { name, age } = person;
const [first, second, third] = [1, 2, 3, 4, 5];

// 5. 默认参数
function greet(name = "访客") {
    return `你好,${name}!`;
}

// 6. 扩展运算符
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];

const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };

// 7. Promise
function fetchData(url) {
    return new Promise((resolve, reject) => {
        fetch(url)
            .then(response => response.json())
            .then(data => resolve(data))
            .catch(error => reject(error));
    });
}

// 使用async/await
async function getUserData() {
    try {
        const response = await fetch('https://api.example.com/users');
        const data = await response.json();
        return data;
    } catch (error) {
        console.error('获取数据失败:', error);
        throw error;
    }
}

// 8. 模块化
// math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;

// main.js
import { add, multiply } from './math.js';
console.log(add(2, 3)); // 5

3.2 DOM操作

// 1. 获取元素
const element = document.getElementById('myElement');
const elements = document.querySelectorAll('.my-class');
const firstElement = document.querySelector('.my-class');

// 2. 创建和插入元素
function createListItem(text) {
    const li = document.createElement('li');
    li.textContent = text;
    li.className = 'list-item';
    return li;
}

// 3. 事件处理
document.addEventListener('DOMContentLoaded', () => {
    const button = document.getElementById('myButton');
    
    // 事件委托
    document.getElementById('list').addEventListener('click', (e) => {
        if (e.target.tagName === 'LI') {
            console.log('点击了列表项:', e.target.textContent);
        }
    });
    
    // 事件对象
    button.addEventListener('click', (e) => {
        e.preventDefault();
        console.log('按钮被点击');
        console.log('鼠标位置:', e.clientX, e.clientY);
    });
    
    // 自定义事件
    const customEvent = new CustomEvent('custom', {
        detail: { message: '自定义事件数据' }
    });
    
    document.addEventListener('custom', (e) => {
        console.log('自定义事件触发:', e.detail.message);
    });
    
    document.dispatchEvent(customEvent);
});

// 4. 表单操作
function handleFormSubmit(event) {
    event.preventDefault();
    
    const formData = new FormData(event.target);
    const data = Object.fromEntries(formData.entries());
    
    // 表单验证
    const email = document.getElementById('email');
    if (!email.value.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
        email.setCustomValidity('请输入有效的邮箱地址');
        email.reportValidity();
        return;
    }
    
    console.log('表单数据:', data);
}

// 5. 动态样式
function toggleTheme() {
    const body = document.body;
    const isDark = body.classList.contains('dark-theme');
    
    if (isDark) {
        body.classList.remove('dark-theme');
        body.style.setProperty('--bg-color', '#ffffff');
        body.style.setProperty('--text-color', '#000000');
    } else {
        body.classList.add('dark-theme');
        body.style.setProperty('--bg-color', '#1a1a1a');
        body.style.setProperty('--text-color', '#ffffff');
    }
}

3.3 异步编程

// 1. 回调函数
function fetchDataWithCallback(url, callback) {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = function() {
        if (xhr.status === 200) {
            callback(null, JSON.parse(xhr.responseText));
        } else {
            callback(new Error('请求失败'));
        }
    };
    xhr.onerror = function() {
        callback(new Error('网络错误'));
    };
    xhr.send();
}

// 2. Promise
function fetchWithPromise(url) {
    return fetch(url)
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP错误! status: ${response.status}`);
            }
            return response.json();
        })
        .catch(error => {
            console.error('请求失败:', error);
            throw error;
        });
}

// 3. async/await
async function fetchUserData() {
    try {
        const response = await fetch('https://api.example.com/users');
        if (!response.ok) {
            throw new Error(`HTTP错误! status: ${response.status}`);
        }
        const users = await response.json();
        
        // 并行请求
        const userPromises = users.slice(0, 5).map(user => 
            fetch(`https://api.example.com/users/${user.id}/details`)
                .then(res => res.json())
        );
        
        const userDetails = await Promise.all(userPromises);
        return { users, userDetails };
        
    } catch (error) {
        console.error('获取用户数据失败:', error);
        // 错误处理
        return { users: [], userDetails: [] };
    }
}

// 4. 错误处理
async function safeFetch(url, options = {}) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 5000);
    
    try {
        const response = await fetch(url, {
            ...options,
            signal: controller.signal
        });
        
        clearTimeout(timeoutId);
        
        if (!response.ok) {
            throw new Error(`HTTP错误! status: ${response.status}`);
        }
        
        return await response.json();
    } catch (error) {
        clearTimeout(timeoutId);
        
        if (error.name === 'AbortError') {
            console.error('请求超时');
        } else if (error.name === 'TypeError') {
            console.error('网络错误');
        } else {
            console.error('其他错误:', error.message);
        }
        
        throw error;
    }
}

第四部分:HTML5高级API

4.1 Canvas绘图

<canvas id="myCanvas" width="800" height="600" style="border:1px solid #000;"></canvas>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 1. 基本绘图
function drawBasicShapes() {
    // 清空画布
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    // 矩形
    ctx.fillStyle = '#ff6600';
    ctx.fillRect(50, 50, 100, 100);
    
    // 圆形
    ctx.beginPath();
    ctx.arc(250, 100, 50, 0, Math.PI * 2);
    ctx.fillStyle = '#0066cc';
    ctx.fill();
    
    // 线条
    ctx.beginPath();
    ctx.moveTo(350, 50);
    ctx.lineTo(450, 150);
    ctx.strokeStyle = '#333';
    ctx.lineWidth = 3;
    ctx.stroke();
    
    // 文字
    ctx.font = '20px Arial';
    ctx.fillStyle = '#000';
    ctx.fillText('Canvas绘图', 50, 200);
}

// 2. 渐变和图案
function drawGradient() {
    // 线性渐变
    const gradient = ctx.createLinearGradient(0, 0, 200, 0);
    gradient.addColorStop(0, '#ff0000');
    gradient.addColorStop(0.5, '#00ff00');
    gradient.addColorStop(1, '#0000ff');
    
    ctx.fillStyle = gradient;
    ctx.fillRect(50, 250, 200, 100);
    
    // 径向渐变
    const radialGradient = ctx.createRadialGradient(400, 300, 10, 400, 300, 50);
    radialGradient.addColorStop(0, '#ffff00');
    radialGradient.addColorStop(1, '#ff00ff');
    
    ctx.fillStyle = radialGradient;
    ctx.beginPath();
    ctx.arc(400, 300, 50, 0, Math.PI * 2);
    ctx.fill();
}

// 3. 动画
let animationId;
let x = 0;

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    // 绘制移动的圆
    ctx.beginPath();
    ctx.arc(x, 100, 20, 0, Math.PI * 2);
    ctx.fillStyle = '#ff0000';
    ctx.fill();
    
    x += 2;
    if (x > canvas.width) {
        x = 0;
    }
    
    animationId = requestAnimationFrame(animate);
}

function stopAnimation() {
    if (animationId) {
        cancelAnimationFrame(animationId);
    }
}

// 4. 图像处理
function processImage() {
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = function() {
        // 绘制原始图像
        ctx.drawImage(img, 0, 0, 300, 200);
        
        // 获取图像数据
        const imageData = ctx.getImageData(0, 0, 300, 200);
        const data = imageData.data;
        
        // 灰度处理
        for (let i = 0; i < data.length; i += 4) {
            const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
            data[i] = avg;     // R
            data[i + 1] = avg; // G
            data[i + 2] = avg; // B
        }
        
        // 将处理后的图像数据放回画布
        ctx.putImageData(imageData, 350, 0);
    };
    img.src = 'https://picsum.photos/300/200';
}

4.2 Web Storage

// 1. localStorage
function saveToLocalStorage(key, value) {
    try {
        localStorage.setItem(key, JSON.stringify(value));
        console.log('数据已保存到localStorage');
    } catch (error) {
        console.error('保存失败:', error);
    }
}

function getFromLocalStorage(key) {
    try {
        const data = localStorage.getItem(key);
        return data ? JSON.parse(data) : null;
    } catch (error) {
        console.error('读取失败:', error);
        return null;
    }
}

function removeFromLocalStorage(key) {
    localStorage.removeItem(key);
}

function clearLocalStorage() {
    localStorage.clear();
}

// 2. sessionStorage
function saveToSessionStorage(key, value) {
    sessionStorage.setItem(key, JSON.stringify(value));
}

// 3. 实际应用:记住用户偏好
function saveUserPreferences() {
    const preferences = {
        theme: 'dark',
        fontSize: 16,
        language: 'zh-CN',
        notifications: true
    };
    
    saveToLocalStorage('userPreferences', preferences);
}

function loadUserPreferences() {
    const preferences = getFromLocalStorage('userPreferences');
    
    if (preferences) {
        // 应用主题
        if (preferences.theme === 'dark') {
            document.body.classList.add('dark-theme');
        }
        
        // 应用字体大小
        document.documentElement.style.fontSize = preferences.fontSize + 'px';
        
        // 应用语言
        // 这里可以调用i18n函数
    }
}

4.3 Web Workers

// worker.js - 在单独的文件中
self.addEventListener('message', function(e) {
    const data = e.data;
    
    // 执行耗时计算
    if (data.type === 'calculate') {
        const result = heavyCalculation(data.value);
        self.postMessage({ type: 'result', result: result });
    }
});

function heavyCalculation(n) {
    // 模拟耗时计算
    let sum = 0;
    for (let i = 0; i < n; i++) {
        sum += Math.sqrt(i);
    }
    return sum;
}

// 主线程代码
function initWorker() {
    if (window.Worker) {
        const worker = new Worker('worker.js');
        
        worker.addEventListener('message', function(e) {
            console.log('从Worker接收到消息:', e.data);
            
            if (e.data.type === 'result') {
                // 更新UI
                document.getElementById('result').textContent = e.data.result;
            }
        });
        
        worker.addEventListener('error', function(e) {
            console.error('Worker错误:', e);
        });
        
        // 发送消息给Worker
        worker.postMessage({
            type: 'calculate',
            value: 1000000
        });
        
        // 通信示例
        const message = {
            type: 'data',
            data: { name: '张三', age: 25 }
        };
        worker.postMessage(message);
        
        // 当不再需要时,终止Worker
        // worker.terminate();
    }
}

第五部分:响应式设计与移动端开发

5.1 媒体查询

/* 移动优先的响应式设计 */
/* 默认样式(移动设备) */
.container {
    width: 100%;
    padding: 10px;
    box-sizing: border-box;
}

.nav {
    display: flex;
    flex-direction: column;
}

.nav-item {
    padding: 10px;
    border-bottom: 1px solid #eee;
}

/* 平板设备(768px及以上) */
@media (min-width: 768px) {
    .container {
        max-width: 720px;
        margin: 0 auto;
    }
    
    .nav {
        flex-direction: row;
        justify-content: space-between;
    }
    
    .nav-item {
        border-bottom: none;
    }
}

/* 桌面设备(992px及以上) */
@media (min-width: 992px) {
    .container {
        max-width: 960px;
    }
    
    .nav-item {
        padding: 15px 20px;
    }
}

/* 大桌面设备(1200px及以上) */
@media (min-width: 1200px) {
    .container {
        max-width: 1140px;
    }
}

/* 高DPI设备(Retina显示屏) */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
    .logo {
        background-image: url('logo@2x.png');
        background-size: contain;
    }
}

/* 横屏/竖屏检测 */
@media (orientation: landscape) {
    .hero-section {
        height: 50vh;
    }
}

@media (orientation: portrait) {
    .hero-section {
        height: 70vh;
    }
}

/* 暗色模式支持 */
@media (prefers-color-scheme: dark) {
    :root {
        --bg-color: #1a1a1a;
        --text-color: #ffffff;
        --border-color: #333;
    }
    
    body {
        background-color: var(--bg-color);
        color: var(--text-color);
    }
    
    .card {
        background-color: #2a2a2a;
        border-color: var(--border-color);
    }
}

5.2 视口单位与弹性布局

/* 视口单位 */
.hero {
    height: 100vh; /* 100%视口高度 */
    width: 100vw;  /* 100%视口宽度 */
    display: flex;
    align-items: center;
    justify-content: center;
}

/* 弹性字体大小 */
html {
    font-size: 16px; /* 基础字体大小 */
}

/* 使用clamp()函数实现响应式字体 */
h1 {
    font-size: clamp(1.5rem, 5vw, 3rem);
}

p {
    font-size: clamp(1rem, 2.5vw, 1.25rem);
}

/* 使用min()和max()函数 */
.container {
    width: min(100%, 1200px); /* 最大1200px,但不超过100% */
    margin: 0 auto;
}

.card {
    width: max(300px, 30%); /* 至少300px,但至少占30% */
}

/* 弹性网格 */
.grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 20px;
}

5.3 移动端交互优化

// 1. 触摸事件
function initTouchEvents() {
    const element = document.getElementById('touch-area');
    
    // 单点触摸
    element.addEventListener('touchstart', function(e) {
        e.preventDefault();
        const touch = e.touches[0];
        console.log('触摸开始:', touch.clientX, touch.clientY);
    });
    
    element.addEventListener('touchmove', function(e) {
        e.preventDefault();
        const touch = e.touches[0];
        console.log('触摸移动:', touch.clientX, touch.clientY);
    });
    
    element.addEventListener('touchend', function(e) {
        console.log('触摸结束');
    });
    
    // 多点触摸
    element.addEventListener('touchstart', function(e) {
        if (e.touches.length > 1) {
            console.log('多点触摸,数量:', e.touches.length);
        }
    });
    
    // 滑动检测
    let startX, startY;
    
    element.addEventListener('touchstart', function(e) {
        startX = e.touches[0].clientX;
        startY = e.touches[0].clientY;
    });
    
    element.addEventListener('touchend', function(e) {
        const endX = e.changedTouches[0].clientX;
        const endY = e.changedTouches[0].clientY;
        
        const diffX = endX - startX;
        const diffY = endY - startY;
        
        if (Math.abs(diffX) > Math.abs(diffY)) {
            if (diffX > 50) {
                console.log('向右滑动');
            } else if (diffX < -50) {
                console.log('向左滑动');
            }
        } else {
            if (diffY > 50) {
                console.log('向下滑动');
            } else if (diffY < -50) {
                console.log('向上滑动');
            }
        }
    });
}

// 2. 防止双击缩放
function preventDoubleTapZoom() {
    let lastTouchEnd = 0;
    
    document.addEventListener('touchend', function(e) {
        const now = Date.now();
        if (now - lastTouchEnd <= 300) {
            e.preventDefault();
        }
        lastTouchEnd = now;
    }, false);
}

// 3. 虚拟键盘处理
function handleVirtualKeyboard() {
    // 检测虚拟键盘是否显示
    const initialHeight = window.innerHeight;
    
    window.addEventListener('resize', function() {
        const currentHeight = window.innerHeight;
        
        if (currentHeight < initialHeight * 0.75) {
            // 虚拟键盘显示
            console.log('虚拟键盘显示');
            // 可以调整布局
            document.body.classList.add('keyboard-open');
        } else {
            // 虚拟键盘隐藏
            console.log('虚拟键盘隐藏');
            document.body.classList.remove('keyboard-open');
        }
    });
}

第六部分:前端工程化与工具链

6.1 模块化开发

// 1. CommonJS(Node.js环境)
// math.js
function add(a, b) {
    return a + b;
}

function multiply(a, b) {
    return a * b;
}

module.exports = {
    add,
    multiply
};

// main.js
const math = require('./math.js');
console.log(math.add(2, 3)); // 5

// 2. ES6模块(浏览器环境)
// utils.js
export const formatDate = (date) => {
    return new Date(date).toLocaleDateString('zh-CN');
};

export const debounce = (fn, delay) => {
    let timer;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, args), delay);
    };
};

// main.js
import { formatDate, debounce } from './utils.js';

// 3. 动态导入
async function loadModule() {
    const module = await import('./dynamic-module.js');
    module.default();
}

// 4. Webpack配置示例
/*
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
        clean: true
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            },
            {
                test: /\.css$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader']
            },
            {
                test: /\.(png|jpg|gif)$/i,
                type: 'asset/resource'
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html'
        }),
        new MiniCssExtractPlugin({
            filename: 'styles.css'
        })
    ],
    devServer: {
        static: './dist',
        port: 8080,
        hot: true
    }
};
*/

6.2 版本控制与协作

# Git基础命令
git init                    # 初始化仓库
git add .                   # 添加所有文件
git commit -m "初始提交"     # 提交代码
git branch feature-login    # 创建新分支
git checkout feature-login  # 切换分支

# 分支管理
git checkout main           # 切换到主分支
git merge feature-login     # 合并分支
git branch -d feature-login # 删除分支

# 远程仓库
git remote add origin https://github.com/username/repo.git
git push -u origin main
git pull origin main

# .gitignore文件示例
/*
!.gitignore
!README.md
!package.json
!webpack.config.js
!src/
!src/**
!public/
!public/**
node_modules/
dist/
*.log
.DS_Store

6.3 代码质量工具

// ESLint配置示例
/*
// .eslintrc.js
module.exports = {
    env: {
        browser: true,
        es2021: true,
        node: true
    },
    extends: [
        'eslint:recommended',
        'plugin:react/recommended',
        'plugin:prettier/recommended'
    ],
    parserOptions: {
        ecmaVersion: 2021,
        sourceType: 'module',
        ecmaFeatures: {
            jsx: true
        }
    },
    rules: {
        'no-console': 'warn',
        'no-unused-vars': 'error',
        'prefer-const': 'error',
        'no-var': 'error',
        'quotes': ['error', 'single'],
        'semi': ['error', 'always']
    }
};
*/

// Prettier配置
/*
// .prettierrc
{
    "singleQuote": true,
    "semi": true,
    "tabWidth": 2,
    "trailingComma": "es5",
    "printWidth": 80
}
*/

// Husky + lint-staged配置
/*
// package.json
{
    "scripts": {
        "lint": "eslint src/**/*.js",
        "format": "prettier --write src/**/*.js",
        "prepare": "husky install"
    },
    "lint-staged": {
        "*.js": [
            "eslint --fix",
            "prettier --write"
        ]
    }
}
*/

第七部分:实战项目:构建一个完整的Todo应用

7.1 项目结构

todo-app/
├── index.html
├── css/
│   ├── style.css
│   └── responsive.css
├── js/
│   ├── app.js
│   ├── storage.js
│   └── ui.js
└── assets/
    └── icons/

7.2 HTML结构

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Todo应用</title>
    <link rel="stylesheet" href="css/style.css">
    <link rel="stylesheet" href="css/responsive.css">
</head>
<body>
    <div class="app-container">
        <header class="app-header">
            <h1>我的待办事项</h1>
            <div class="theme-toggle">
                <button id="themeBtn">🌙</button>
            </div>
        </header>
        
        <main class="app-main">
            <form id="todoForm" class="todo-form">
                <input 
                    type="text" 
                    id="todoInput" 
                    placeholder="添加新的待办事项..." 
                    required
                    maxlength="100"
                >
                <button type="submit">添加</button>
            </form>
            
            <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 id="todoList" class="todo-list">
                <!-- 动态生成 -->
            </ul>
            
            <div class="stats">
                <span id="total">总计: 0</span>
                <span id="completed">已完成: 0</span>
                <span id="active">未完成: 0</span>
            </div>
        </main>
        
        <footer class="app-footer">
            <p>© 2023 Todo应用 | 使用HTML5, CSS3, JavaScript构建</p>
        </footer>
    </div>
    
    <script src="js/storage.js"></script>
    <script src="js/ui.js"></script>
    <script src="js/app.js"></script>
</body>
</html>

7.3 CSS样式

/* css/style.css */
:root {
    --bg-color: #f5f5f5;
    --text-color: #333;
    --primary-color: #0066cc;
    --secondary-color: #666;
    --border-color: #ddd;
    --completed-color: #999;
    --shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.dark-theme {
    --bg-color: #1a1a1a;
    --text-color: #fff;
    --primary-color: #4d94ff;
    --secondary-color: #aaa;
    --border-color: #333;
    --completed-color: #666;
    --shadow: 0 2px 10px rgba(0,0,0,0.3);
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background-color: var(--bg-color);
    color: var(--text-color);
    line-height: 1.6;
    transition: background-color 0.3s, color 0.3s;
}

.app-container {
    max-width: 600px;
    margin: 0 auto;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    padding: 20px;
}

.app-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 30px;
    padding-bottom: 20px;
    border-bottom: 1px solid var(--border-color);
}

.app-header h1 {
    font-size: 1.8rem;
    color: var(--primary-color);
}

.theme-toggle button {
    background: none;
    border: none;
    font-size: 1.5rem;
    cursor: pointer;
    padding: 5px;
    border-radius: 50%;
    transition: background-color 0.2s;
}

.theme-toggle button:hover {
    background-color: var(--border-color);
}

.todo-form {
    display: flex;
    gap: 10px;
    margin-bottom: 20px;
}

.todo-form input {
    flex: 1;
    padding: 12px 15px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    font-size: 1rem;
    background-color: var(--bg-color);
    color: var(--text-color);
    transition: border-color 0.2s;
}

.todo-form input:focus {
    outline: none;
    border-color: var(--primary-color);
}

.todo-form button {
    padding: 12px 20px;
    background-color: var(--primary-color);
    color: white;
    border: none;
    border-radius: 6px;
    font-size: 1rem;
    cursor: pointer;
    transition: background-color 0.2s;
}

.todo-form button:hover {
    background-color: #0052a3;
}

.filters {
    display: flex;
    gap: 10px;
    margin-bottom: 20px;
    flex-wrap: wrap;
}

.filter-btn {
    padding: 8px 16px;
    background-color: transparent;
    border: 1px solid var(--border-color);
    border-radius: 20px;
    color: var(--text-color);
    cursor: pointer;
    transition: all 0.2s;
}

.filter-btn.active {
    background-color: var(--primary-color);
    color: white;
    border-color: var(--primary-color);
}

.filter-btn:hover:not(.active) {
    background-color: var(--border-color);
}

.todo-list {
    list-style: none;
    margin-bottom: 20px;
}

.todo-item {
    display: flex;
    align-items: center;
    padding: 15px;
    background-color: var(--bg-color);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    margin-bottom: 10px;
    transition: all 0.2s;
    animation: slideIn 0.3s ease-out;
}

.todo-item:hover {
    box-shadow: var(--shadow);
    transform: translateY(-1px);
}

.todo-item.completed {
    opacity: 0.6;
}

.todo-item.completed .todo-text {
    text-decoration: line-through;
    color: var(--completed-color);
}

.todo-checkbox {
    margin-right: 15px;
    width: 20px;
    height: 20px;
    cursor: pointer;
}

.todo-text {
    flex: 1;
    font-size: 1rem;
    word-break: break-word;
}

.todo-actions {
    display: flex;
    gap: 8px;
}

.todo-actions button {
    background: none;
    border: none;
    cursor: pointer;
    padding: 5px;
    border-radius: 4px;
    transition: background-color 0.2s;
}

.todo-actions button:hover {
    background-color: var(--border-color);
}

.delete-btn {
    color: #e74c3c;
}

.edit-btn {
    color: var(--primary-color);
}

.stats {
    display: flex;
    justify-content: space-around;
    padding: 15px;
    background-color: var(--bg-color);
    border-radius: 8px;
    border: 1px solid var(--border-color);
    font-size: 0.9rem;
}

.stats span {
    font-weight: 500;
}

.app-footer {
    margin-top: auto;
    text-align: center;
    padding: 20px;
    color: var(--secondary-color);
    font-size: 0.85rem;
}

/* 动画 */
@keyframes slideIn {
    from {
        opacity: 0;
        transform: translateY(-10px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes fadeOut {
    to {
        opacity: 0;
        transform: translateX(20px);
    }
}

/* 响应式设计 */
@media (max-width: 480px) {
    .app-container {
        padding: 15px;
    }
    
    .app-header h1 {
        font-size: 1.4rem;
    }
    
    .todo-form {
        flex-direction: column;
    }
    
    .todo-form button {
        width: 100%;
    }
    
    .filters {
        justify-content: center;
    }
    
    .stats {
        flex-direction: column;
        gap: 8px;
        align-items: center;
    }
}

7.4 JavaScript实现

7.4.1 storage.js - 数据存储模块

// js/storage.js
class TodoStorage {
    constructor() {
        this.STORAGE_KEY = 'todo-app-data';
    }
    
    // 获取所有待办事项
    getTodos() {
        try {
            const data = localStorage.getItem(this.STORAGE_KEY);
            return data ? JSON.parse(data) : [];
        } catch (error) {
            console.error('读取数据失败:', error);
            return [];
        }
    }
    
    // 保存待办事项
    saveTodos(todos) {
        try {
            localStorage.setItem(this.STORAGE_KEY, JSON.stringify(todos));
            return true;
        } catch (error) {
            console.error('保存数据失败:', error);
            return false;
        }
    }
    
    // 添加待办事项
    addTodo(text) {
        const todos = this.getTodos();
        const newTodo = {
            id: Date.now().toString(),
            text: text,
            completed: false,
            createdAt: new Date().toISOString()
        };
        
        todos.push(newTodo);
        this.saveTodos(todos);
        return newTodo;
    }
    
    // 更新待办事项
    updateTodo(id, updates) {
        const todos = this.getTodos();
        const index = todos.findIndex(todo => todo.id === id);
        
        if (index !== -1) {
            todos[index] = { ...todos[index], ...updates };
            this.saveTodos(todos);
            return todos[index];
        }
        
        return null;
    }
    
    // 删除待办事项
    deleteTodo(id) {
        const todos = this.getTodos();
        const filtered = todos.filter(todo => todo.id !== id);
        this.saveTodos(filtered);
        return filtered;
    }
    
    // 切换完成状态
    toggleTodo(id) {
        const todo = this.getTodoById(id);
        if (todo) {
            return this.updateTodo(id, { completed: !todo.completed });
        }
        return null;
    }
    
    // 根据ID获取待办事项
    getTodoById(id) {
        const todos = this.getTodos();
        return todos.find(todo => todo.id === id);
    }
    
    // 清空所有待办事项
    clearAll() {
        localStorage.removeItem(this.STORAGE_KEY);
    }
    
    // 获取统计信息
    getStats() {
        const todos = this.getTodos();
        const total = todos.length;
        const completed = todos.filter(todo => todo.completed).length;
        const active = total - completed;
        
        return { total, completed, active };
    }
}

7.4.2 ui.js - UI操作模块

// js/ui.js
class TodoUI {
    constructor() {
        this.elements = {
            todoForm: document.getElementById('todoForm'),
            todoInput: document.getElementById('todoInput'),
            todoList: document.getElementById('todoList'),
            filterBtns: document.querySelectorAll('.filter-btn'),
            total: document.getElementById('total'),
            completed: document.getElementById('completed'),
            active: document.getElementById('active'),
            themeBtn: document.getElementById('themeBtn')
        };
        
        this.currentFilter = 'all';
    }
    
    // 渲染待办事项列表
    renderTodos(todos) {
        const filteredTodos = this.filterTodos(todos);
        this.elements.todoList.innerHTML = '';
        
        if (filteredTodos.length === 0) {
            this.elements.todoList.innerHTML = `
                <li class="empty-state">
                    <p>暂无待办事项</p>
                    <small>添加一个试试吧!</small>
                </li>
            `;
            return;
        }
        
        filteredTodos.forEach(todo => {
            const li = this.createTodoElement(todo);
            this.elements.todoList.appendChild(li);
        });
    }
    
    // 创建待办事项元素
    createTodoElement(todo) {
        const li = document.createElement('li');
        li.className = `todo-item ${todo.completed ? 'completed' : ''}`;
        li.dataset.id = todo.id;
        
        li.innerHTML = `
            <input 
                type="checkbox" 
                class="todo-checkbox" 
                ${todo.completed ? 'checked' : ''}
                aria-label="标记完成"
            >
            <span class="todo-text">${this.escapeHtml(todo.text)}</span>
            <div class="todo-actions">
                <button class="edit-btn" title="编辑">✏️</button>
                <button class="delete-btn" title="删除">🗑️</button>
            </div>
        `;
        
        return li;
    }
    
    // 过滤待办事项
    filterTodos(todos) {
        switch (this.currentFilter) {
            case 'active':
                return todos.filter(todo => !todo.completed);
            case 'completed':
                return todos.filter(todo => todo.completed);
            default:
                return todos;
        }
    }
    
    // 更新统计信息
    updateStats(stats) {
        this.elements.total.textContent = `总计: ${stats.total}`;
        this.elements.completed.textContent = `已完成: ${stats.completed}`;
        this.elements.active.textContent = `未完成: ${stats.active}`;
    }
    
    // 设置当前过滤器
    setFilter(filter) {
        this.currentFilter = filter;
        
        // 更新按钮状态
        this.elements.filterBtns.forEach(btn => {
            if (btn.dataset.filter === filter) {
                btn.classList.add('active');
            } else {
                btn.classList.remove('active');
            }
        });
    }
    
    // 清空输入框
    clearInput() {
        this.elements.todoInput.value = '';
        this.elements.todoInput.focus();
    }
    
    // 显示错误消息
    showError(message) {
        const errorDiv = document.createElement('div');
        errorDiv.className = 'error-message';
        errorDiv.textContent = message;
        errorDiv.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background-color: #e74c3c;
            color: white;
            padding: 10px 15px;
            border-radius: 4px;
            z-index: 1000;
            animation: slideIn 0.3s ease-out;
        `;
        
        document.body.appendChild(errorDiv);
        
        setTimeout(() => {
            errorDiv.style.animation = 'fadeOut 0.3s ease-out forwards';
            setTimeout(() => errorDiv.remove(), 300);
        }, 3000);
    }
    
    // 显示成功消息
    showSuccess(message) {
        const successDiv = document.createElement('div');
        successDiv.className = 'success-message';
        successDiv.textContent = message;
        successDiv.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background-color: #27ae60;
            color: white;
            padding: 10px 15px;
            border-radius: 4px;
            z-index: 1000;
            animation: slideIn 0.3s ease-out;
        `;
        
        document.body.appendChild(successDiv);
        
        setTimeout(() => {
            successDiv.style.animation = 'fadeOut 0.3s ease-out forwards';
            setTimeout(() => successDiv.remove(), 300);
        }, 2000);
    }
    
    // 编辑待办事项
    editTodo(id, text) {
        const todoElement = document.querySelector(`[data-id="${id}"]`);
        if (!todoElement) return;
        
        const textSpan = todoElement.querySelector('.todo-text');
        const originalText = textSpan.textContent;
        
        // 创建编辑输入框
        const input = document.createElement('input');
        input.type = 'text';
        input.value = originalText;
        input.className = 'edit-input';
        input.style.cssText = `
            flex: 1;
            padding: 5px;
            border: 1px solid var(--primary-color);
            border-radius: 4px;
            font-size: 1rem;
        `;
        
        // 替换文本为输入框
        textSpan.replaceWith(input);
        input.focus();
        input.select();
        
        // 保存或取消编辑
        const saveEdit = () => {
            const newText = input.value.trim();
            if (newText && newText !== originalText) {
                return newText;
            }
            return originalText;
        };
        
        return { input, saveEdit };
    }
    
    // 工具函数:HTML转义
    escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }
    
    // 切换主题
    toggleTheme() {
        document.body.classList.toggle('dark-theme');
        const isDark = document.body.classList.contains('dark-theme');
        this.elements.themeBtn.textContent = isDark ? '☀️' : '🌙';
        
        // 保存主题偏好
        localStorage.setItem('theme', isDark ? 'dark' : 'light');
    }
    
    // 加载主题
    loadTheme() {
        const savedTheme = localStorage.getItem('theme');
        if (savedTheme === 'dark') {
            document.body.classList.add('dark-theme');
            this.elements.themeBtn.textContent = '☀️';
        }
    }
}

7.4.3 app.js - 主应用逻辑

// js/app.js
class TodoApp {
    constructor() {
        this.storage = new TodoStorage();
        this.ui = new TodoUI();
        this.init();
    }
    
    init() {
        // 加载主题
        this.ui.loadTheme();
        
        // 加载并渲染待办事项
        this.loadTodos();
        
        // 绑定事件
        this.bindEvents();
    }
    
    loadTodos() {
        const todos = this.storage.getTodos();
        this.ui.renderTodos(todos);
        this.ui.updateStats(this.storage.getStats());
    }
    
    bindEvents() {
        // 表单提交
        this.ui.elements.todoForm.addEventListener('submit', (e) => {
            e.preventDefault();
            this.handleAddTodo();
        });
        
        // 过滤器按钮
        this.ui.elements.filterBtns.forEach(btn => {
            btn.addEventListener('click', () => {
                const filter = btn.dataset.filter;
                this.ui.setFilter(filter);
                this.loadTodos();
            });
        });
        
        // 待办事项列表事件委托
        this.ui.elements.todoList.addEventListener('click', (e) => {
            const todoItem = e.target.closest('.todo-item');
            if (!todoItem) return;
            
            const id = todoItem.dataset.id;
            
            // 复选框点击
            if (e.target.classList.contains('todo-checkbox')) {
                this.handleToggleTodo(id);
            }
            // 删除按钮点击
            else if (e.target.classList.contains('delete-btn')) {
                this.handleDeleteTodo(id);
            }
            // 编辑按钮点击
            else if (e.target.classList.contains('edit-btn')) {
                this.handleEditTodo(id);
            }
        });
        
        // 主题切换
        this.ui.elements.themeBtn.addEventListener('click', () => {
            this.ui.toggleTheme();
        });
        
        // 键盘快捷键
        document.addEventListener('keydown', (e) => {
            // Ctrl/Cmd + Enter 提交
            if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
                this.handleAddTodo();
            }
            
            // Escape 取消编辑
            if (e.key === 'Escape') {
                const editInput = document.querySelector('.edit-input');
                if (editInput) {
                    const todoItem = editInput.closest('.todo-item');
                    const id = todoItem.dataset.id;
                    const todo = this.storage.getTodoById(id);
                    if (todo) {
                        this.ui.renderTodos(this.storage.getTodos());
                    }
                }
            }
        });
    }
    
    handleAddTodo() {
        const text = this.ui.elements.todoInput.value.trim();
        
        if (!text) {
            this.ui.showError('请输入待办事项内容');
            return;
        }
        
        if (text.length > 100) {
            this.ui.showError('内容不能超过100个字符');
            return;
        }
        
        try {
            const todo = this.storage.addTodo(text);
            this.ui.clearInput();
            this.loadTodos();
            this.ui.showSuccess('待办事项添加成功!');
        } catch (error) {
            this.ui.showError('添加失败,请重试');
            console.error('添加待办事项失败:', error);
        }
    }
    
    handleToggleTodo(id) {
        try {
            const todo = this.storage.toggleTodo(id);
            if (todo) {
                this.loadTodos();
            }
        } catch (error) {
            this.ui.showError('操作失败,请重试');
            console.error('切换状态失败:', error);
        }
    }
    
    handleDeleteTodo(id) {
        // 确认删除
        if (!confirm('确定要删除这个待办事项吗?')) {
            return;
        }
        
        try {
            this.storage.deleteTodo(id);
            this.loadTodos();
            this.ui.showSuccess('待办事项已删除');
        } catch (error) {
            this.ui.showError('删除失败,请重试');
            console.error('删除待办事项失败:', error);
        }
    }
    
    handleEditTodo(id) {
        const todo = this.storage.getTodoById(id);
        if (!todo) return;
        
        const { input, saveEdit } = this.ui.editTodo(id, todo.text);
        
        const finishEdit = () => {
            const newText = saveEdit();
            
            if (newText !== todo.text) {
                try {
                    this.storage.updateTodo(id, { text: newText });
                    this.loadTodos();
                    this.ui.showSuccess('待办事项已更新');
                } catch (error) {
                    this.ui.showError('更新失败,请重试');
                    console.error('更新待办事项失败:', error);
                }
            } else {
                // 没有变化,重新渲染
                this.ui.renderTodos(this.storage.getTodos());
            }
        };
        
        // 绑定事件
        input.addEventListener('blur', finishEdit);
        input.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                finishEdit();
            }
        });
    }
}

// 应用初始化
document.addEventListener('DOMContentLoaded', () => {
    new TodoApp();
});

7.5 项目优化与扩展

7.5.1 添加拖拽排序功能

// 在TodoUI类中添加拖拽功能
class TodoUIWithDragDrop extends TodoUI {
    constructor() {
        super();
        this.draggedElement = null;
        this.initDragDrop();
    }
    
    initDragDrop() {
        this.elements.todoList.addEventListener('dragstart', (e) => {
            if (e.target.classList.contains('todo-item')) {
                this.draggedElement = e.target;
                e.target.style.opacity = '0.5';
            }
        });
        
        this.elements.todoList.addEventListener('dragend', (e) => {
            if (e.target.classList.contains('todo-item')) {
                e.target.style.opacity = '';
                this.draggedElement = null;
            }
        });
        
        this.elements.todoList.addEventListener('dragover', (e) => {
            e.preventDefault();
            const afterElement = this.getDragAfterElement(e.clientY);
            const draggable = this.draggedElement;
            
            if (afterElement == null) {
                this.elements.todoList.appendChild(draggable);
            } else {
                this.elements.todoList.insertBefore(draggable, afterElement);
            }
        });
    }
    
    getDragAfterElement(y) {
        const draggableElements = [...this.elements.todoList.querySelectorAll('.todo-item:not(.dragging)')];
        
        return draggableElements.reduce((closest, child) => {
            const box = child.getBoundingClientRect();
            const offset = y - box.top - box.height / 2;
            
            if (offset < 0 && offset > closest.offset) {
                return { offset: offset, element: child };
            } else {
                return closest;
            }
        }, { offset: Number.NEGATIVE_INFINITY }).element;
    }
}

7.5.2 添加数据导出/导入功能

// 在TodoStorage类中添加数据导出/导入
class TodoStorageWithExport extends TodoStorage {
    // 导出数据为JSON文件
    exportData() {
        const todos = this.getTodos();
        const dataStr = JSON.stringify(todos, null, 2);
        const dataBlob = new Blob([dataStr], { type: 'application/json' });
        
        const url = URL.createObjectURL(dataBlob);
        const link = document.createElement('a');
        link.href = url;
        link.download = `todo-backup-${new Date().toISOString().split('T')[0]}.json`;
        link.click();
        
        URL.revokeObjectURL(url);
    }
    
    // 从JSON文件导入数据
    importData(file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            
            reader.onload = (e) => {
                try {
                    const importedTodos = JSON.parse(e.target.result);
                    
                    // 验证数据格式
                    if (!Array.isArray(importedTodos)) {
                        throw new Error('无效的数据格式');
                    }
                    
                    // 合并数据(避免重复)
                    const existingTodos = this.getTodos();
                    const mergedTodos = [...existingTodos];
                    
                    importedTodos.forEach(importedTodo => {
                        if (!existingTodos.some(todo => todo.id === importedTodo.id)) {
                            mergedTodos.push(importedTodo);
                        }
                    });
                    
                    this.saveTodos(mergedTodos);
                    resolve(mergedTodos);
                } catch (error) {
                    reject(error);
                }
            };
            
            reader.onerror = () => {
                reject(new Error('文件读取失败'));
            };
            
            reader.readAsText(file);
        });
    }
}

7.5.3 添加搜索功能

// 在TodoUI类中添加搜索功能
class TodoUIWithSearch extends TodoUI {
    constructor() {
        super();
        this.searchTerm = '';
        this.initSearch();
    }
    
    initSearch() {
        // 创建搜索框
        const searchContainer = document.createElement('div');
        searchContainer.className = 'search-container';
        searchContainer.innerHTML = `
            <input 
                type="text" 
                id="searchInput" 
                placeholder="搜索待办事项..." 
                class="search-input"
            >
            <button id="clearSearch" class="search-clear" title="清除搜索">✕</button>
        `;
        
        // 插入到表单之后
        this.elements.todoForm.parentNode.insertBefore(
            searchContainer, 
            this.elements.todoForm.nextSibling
        );
        
        // 绑定事件
        const searchInput = document.getElementById('searchInput');
        const clearSearch = document.getElementById('clearSearch');
        
        searchInput.addEventListener('input', (e) => {
            this.searchTerm = e.target.value.toLowerCase();
            this.renderTodos(this.storage.getTodos());
        });
        
        clearSearch.addEventListener('click', () => {
            searchInput.value = '';
            this.searchTerm = '';
            this.renderTodos(this.storage.getTodos());
        });
    }
    
    // 重写过滤方法,添加搜索过滤
    filterTodos(todos) {
        let filtered = super.filterTodos(todos);
        
        if (this.searchTerm) {
            filtered = filtered.filter(todo => 
                todo.text.toLowerCase().includes(this.searchTerm)
            );
        }
        
        return filtered;
    }
}

第八部分:性能优化与最佳实践

8.1 性能优化技巧

// 1. 防抖和节流
function debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

function throttle(func, limit) {
    let inThrottle;
    return function() {
        const args = arguments;
        const context = this;
        if (!inThrottle) {
            func.apply(context, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}

// 2. 图片懒加载
function lazyLoadImages() {
    const images = document.querySelectorAll('img[data-src]');
    
    const imageObserver = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                const img = entry.target;
                img.src = img.dataset.src;
                img.classList.remove('lazy');
                observer.unobserve(img);
            }
        });
    });
    
    images.forEach(img => imageObserver.observe(img));
}

// 3. 虚拟滚动
class VirtualScroll {
    constructor(container, itemHeight, totalItems, renderItem) {
        this.container = container;
        this.itemHeight = itemHeight;
        this.totalItems = totalItems;
        this.renderItem = renderItem;
        this.visibleCount = 0;
        this.scrollTop = 0;
        
        this.init();
    }
    
    init() {
        this.container.style.height = '400px';
        this.container.style.overflowY = 'auto';
        this.container.style.position = 'relative';
        
        this.visibleCount = Math.ceil(400 / this.itemHeight) + 2;
        
        this.container.addEventListener('scroll', throttle(() => {
            this.render();
        }, 50));
        
        this.render();
    }
    
    render() {
        const scrollTop = this.container.scrollTop;
        const startIndex = Math.floor(scrollTop / this.itemHeight);
        const endIndex = Math.min(startIndex + this.visibleCount, this.totalItems);
        
        // 清空容器
        this.container.innerHTML = '';
        
        // 创建占位元素
        const totalHeight = this.totalItems * this.itemHeight;
        const placeholder = document.createElement('div');
        placeholder.style.height = `${totalHeight}px`;
        placeholder.style.position = 'absolute';
        placeholder.style.top = '0';
        placeholder.style.left = '0';
        placeholder.style.right = '0';
        this.container.appendChild(placeholder);
        
        // 渲染可见项
        for (let i = startIndex; i < endIndex; i++) {
            const item = this.renderItem(i);
            item.style.position = 'absolute';
            item.style.top = `${i * this.itemHeight}px`;
            item.style.height = `${this.itemHeight}px`;
            item.style.width = '100%';
            this.container.appendChild(item);
        }
    }
}

8.2 代码组织与架构

// 1. 模块化架构示例
// src/
//   ├── core/
//   │   ├── event-bus.js
//   │   ├── state-manager.js
//   │   └── utils.js
//   ├── components/
//   │   ├── TodoItem.js
//   │   ├── TodoList.js
//   │   └── TodoForm.js
//   ├── services/
//   │   ├── api.js
//   │   └── storage.js
//   ├── views/
//   │   ├── HomeView.js
//   │   └── SettingsView.js
//   └── main.js

// 2. 状态管理(简单实现)
class StateManager {
    constructor(initialState = {}) {
        this.state = initialState;
        this.listeners = [];
    }
    
    getState() {
        return this.state;
    }
    
    setState(updater) {
        const newState = typeof updater === 'function' 
            ? updater(this.state) 
            : { ...this.state, ...updater };
        
        this.state = newState;
        this.notify();
    }
    
    subscribe(listener) {
        this.listeners.push(listener);
        return () => {
            this.listeners = this.listeners.filter(l => l !== listener);
        };
    }
    
    notify() {
        this.listeners.forEach(listener => listener(this.state));
    }
}

// 3. 事件总线
class EventBus {
    constructor() {
        this.events = {};
    }
    
    on(event, callback) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push(callback);
    }
    
    off(event, callback) {
        if (!this.events[event]) return;
        this.events[event] = this.events[event].filter(cb => cb !== callback);
    }
    
    emit(event, data) {
        if (!this.events[event]) return;
        this.events[event].forEach(callback => callback(data));
    }
}

8.3 测试基础

// 1. 单元测试示例(使用Jest)
/*
// math.test.js
const { add, multiply } = require('./math');

describe('Math functions', () => {
    test('add should return correct sum', () => {
        expect(add(2, 3)).toBe(5);
        expect(add(-1, 1)).toBe(0);
    });
    
    test('multiply should return correct product', () => {
        expect(multiply(2, 3)).toBe(6);
        expect(multiply(-2, 3)).toBe(-6);
    });
});

// todo.test.js
const TodoStorage = require('./storage');

describe('TodoStorage', () => {
    let storage;
    
    beforeEach(() => {
        storage = new TodoStorage();
        storage.clearAll();
    });
    
    test('should add a todo', () => {
        const todo = storage.addTodo('Test todo');
        expect(todo.text).toBe('Test todo');
        expect(todo.completed).toBe(false);
        expect(storage.getTodos().length).toBe(1);
    });
    
    test('should toggle todo completion', () => {
        const todo = storage.addTodo('Test todo');
        const toggled = storage.toggleTodo(todo.id);
        expect(toggled.completed).toBe(true);
    });
});
*/

// 2. 端到端测试示例(使用Cypress)
/*
// cypress/integration/todo.spec.js
describe('Todo App', () => {
    beforeEach(() => {
        cy.visit('/');
    });
    
    it('should add a new todo', () => {
        cy.get('#todoInput').type('Buy groceries');
        cy.get('#todoForm').submit();
        
        cy.get('.todo-item').should('have.length', 1);
        cy.get('.todo-text').should('contain', 'Buy groceries');
    });
    
    it('should mark todo as completed', () => {
        cy.get('#todoInput').type('Complete task');
        cy.get('#todoForm').submit();
        
        cy.get('.todo-checkbox').click();
        cy.get('.todo-item').should('have.class', 'completed');
    });
    
    it('should filter todos', () => {
        // 添加多个待办事项
        ['Task 1', 'Task 2', 'Task 3'].forEach(task => {
            cy.get('#todoInput').type(task);
            cy.get('#todoForm').submit();
        });
        
        // 标记第一个为完成
        cy.get('.todo-checkbox').first().click();
        
        // 过滤未完成
        cy.get('[data-filter="active"]').click();
        cy.get('.todo-item').should('have.length', 2);
        
        // 过滤已完成
        cy.get('[data-filter="completed"]').click();
        cy.get('.todo-item').should('have.length', 1);
    });
});
*/

第九部分:学习资源与进阶路径

9.1 推荐学习资源

  1. 官方文档

  2. 在线课程

  3. 书籍推荐

    • 《JavaScript高级程序设计》
    • 《CSS世界》
    • 《深入浅出Vue.js》
    • 《Web性能权威指南》
  4. 工具与框架

9.2 进阶学习路径

  1. JavaScript深度

    • 原型与继承
    • 闭包与作用域
    • 异步编程深度
    • ES6+新特性
    • 设计模式
  2. 前端框架

    • React生态(Redux, React Router)
    • Vue生态(Vuex, Vue Router)
    • 状态管理(MobX, Pinia)
    • 服务端渲染(Next.js, Nuxt.js)
  3. 工程化

    • Webpack/Vite配置
    • TypeScript
    • CI/CD
    • 微前端架构
  4. 性能优化

    • 渲染性能优化
    • 网络性能优化
    • 内存管理
    • 监控与调试
  5. 跨平台

    • React Native
    • Electron
    • PWA(渐进式Web应用)
    • 小程序开发

9.3 实战项目建议

  1. 初级项目

    • 个人博客系统
    • 天气预报应用
    • 股票行情查看器
    • 在线Markdown编辑器
  2. 中级项目

    • 电商网站前端
    • 社交媒体应用
    • 在线协作文档
    • 数据可视化仪表盘
  3. 高级项目

    • 完整的CMS系统
    • 实时聊天应用
    • 在线代码编辑器
    • 移动端混合应用

第十部分:总结与展望

10.1 核心技能回顾

通过本文的学习,你已经掌握了HTML5前端开发的核心技能:

  1. HTML5基础:语义化标签、表单增强、多媒体支持
  2. CSS3核心:选择器、布局(Flexbox/Grid)、动画、响应式设计
  3. JavaScript核心:ES6+特性、DOM操作、异步编程
  4. HTML5高级API:Canvas、Web Storage、Web Workers
  5. 前端工程化:模块化、版本控制、代码质量工具
  6. 实战项目:完整的Todo应用开发

10.2 持续学习建议

  1. 保持实践:每周至少完成一个小项目
  2. 阅读源码:学习优秀开源项目的代码结构
  3. 参与社区:GitHub、Stack Overflow、技术论坛
  4. 关注趋势:WebAssembly、Web Components、Web3等新技术
  5. 构建作品集:将项目部署到GitHub Pages或Vercel

10.3 未来发展方向

前端开发正在向以下方向发展:

  1. 全栈化:Node.js、数据库、服务器部署
  2. 智能化:AI辅助开发、代码生成
  3. 跨平台:一次开发,多端运行
  4. 性能极致:Web Vitals、Core Web Vitals优化
  5. 无障碍:Web Accessibility (a11y)

10.4 最后的建议

前端开发是一个快速变化的领域,但核心原理是相对稳定的。建议你:

  1. 打好基础:不要急于学习框架,先掌握原生技术
  2. 注重实践:理论结合实践,多写代码
  3. 培养思维:解决问题的思维比具体技术更重要
  4. 保持好奇:对新技术保持开放和学习的态度
  5. 享受过程:编程应该是有趣的,享受创造的过程

通过系统学习和持续实践,你一定能够成为一名优秀的前端开发者。祝你学习顺利,前程似锦!