引言
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>© 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 推荐学习资源
官方文档
- MDN Web Docs (https://developer.mozilla.org)
- HTML5规范 (https://html.spec.whatwg.org)
- CSS规范 (https://www.w3.org/TR/CSS/)
在线课程
- freeCodeCamp (https://www.freecodecamp.org)
- The Odin Project (https://www.theodinproject.com)
- Frontend Masters (https://frontendmasters.com)
书籍推荐
- 《JavaScript高级程序设计》
- 《CSS世界》
- 《深入浅出Vue.js》
- 《Web性能权威指南》
工具与框架
- React (https://reactjs.org)
- Vue.js (https://vuejs.org)
- Svelte (https://svelte.dev)
- Next.js (https://nextjs.org)
9.2 进阶学习路径
JavaScript深度
- 原型与继承
- 闭包与作用域
- 异步编程深度
- ES6+新特性
- 设计模式
前端框架
- React生态(Redux, React Router)
- Vue生态(Vuex, Vue Router)
- 状态管理(MobX, Pinia)
- 服务端渲染(Next.js, Nuxt.js)
工程化
- Webpack/Vite配置
- TypeScript
- CI/CD
- 微前端架构
性能优化
- 渲染性能优化
- 网络性能优化
- 内存管理
- 监控与调试
跨平台
- React Native
- Electron
- PWA(渐进式Web应用)
- 小程序开发
9.3 实战项目建议
初级项目
- 个人博客系统
- 天气预报应用
- 股票行情查看器
- 在线Markdown编辑器
中级项目
- 电商网站前端
- 社交媒体应用
- 在线协作文档
- 数据可视化仪表盘
高级项目
- 完整的CMS系统
- 实时聊天应用
- 在线代码编辑器
- 移动端混合应用
第十部分:总结与展望
10.1 核心技能回顾
通过本文的学习,你已经掌握了HTML5前端开发的核心技能:
- HTML5基础:语义化标签、表单增强、多媒体支持
- CSS3核心:选择器、布局(Flexbox/Grid)、动画、响应式设计
- JavaScript核心:ES6+特性、DOM操作、异步编程
- HTML5高级API:Canvas、Web Storage、Web Workers
- 前端工程化:模块化、版本控制、代码质量工具
- 实战项目:完整的Todo应用开发
10.2 持续学习建议
- 保持实践:每周至少完成一个小项目
- 阅读源码:学习优秀开源项目的代码结构
- 参与社区:GitHub、Stack Overflow、技术论坛
- 关注趋势:WebAssembly、Web Components、Web3等新技术
- 构建作品集:将项目部署到GitHub Pages或Vercel
10.3 未来发展方向
前端开发正在向以下方向发展:
- 全栈化:Node.js、数据库、服务器部署
- 智能化:AI辅助开发、代码生成
- 跨平台:一次开发,多端运行
- 性能极致:Web Vitals、Core Web Vitals优化
- 无障碍:Web Accessibility (a11y)
10.4 最后的建议
前端开发是一个快速变化的领域,但核心原理是相对稳定的。建议你:
- 打好基础:不要急于学习框架,先掌握原生技术
- 注重实践:理论结合实践,多写代码
- 培养思维:解决问题的思维比具体技术更重要
- 保持好奇:对新技术保持开放和学习的态度
- 享受过程:编程应该是有趣的,享受创造的过程
通过系统学习和持续实践,你一定能够成为一名优秀的前端开发者。祝你学习顺利,前程似锦!
