【前端基础】HTML + CSS + JavaScript 进阶(一)
前言
基础系列教你"怎么写代码",进阶系列教你"如何写好代码"。
如果用烹饪来比喻:
• 基础系列:教你认识食材,掌握基本的刀工和火候
• 进阶系列:教你食材搭配、营养搭配、摆盘艺术、厨房管理
这篇进阶文章,我们将聚焦三个核心方向:
1. 性能优化:让页面加载更快、运行更流畅
2. 代码质量:让代码更易维护、更健壮
3. 工程化实践:用现代工具提升开发效率
准备好了吗?让我们从"会写代码"迈向"专业开发"!
第一部分:前端性能优化
性能优化是前端工程师的核心竞争力。一个页面,功能再强大,如果加载慢、操作卡顿,用户也会流失。
1.1 网页加载流程
理解性能优化,首先要理解网页的加载流程。
┌─────────────────────────────────────────────────────────┐ │ 网页加载完整流程 │ ├─────────────────────────────────────────────────────────┤ │ 1. DNS解析(域名 → IP地址) │ │ ↓ │ │ 2. TCP连接(建立客户端-服务器的通信通道) │ │ ↓ │ │ 3. 发送HTTP请求(请求HTML文件) │ │ ↓ │ │ 4. 服务器响应(返回HTML) │ │ ↓ │ │ 5. 解析HTML,构建DOM树 │ │ ↓ │ │ 6. 解析CSS,构建CSSOM树 │ │ ↓ │ │ 7. 合并DOM和CSSOM,生成渲染树 │ │ ↓ │ │ 8. 布局(计算元素位置和大小) │ │ ↓ │ │ 9. 绘制(绘制像素到屏幕) │ │ ↓ │ │ 10. 显示页面 │ └─────────────────────────────────────────────────────────┘性能优化的目标:缩短每个环节的时间,提升整体加载速度。
1.2 关键性能指标
在优化之前,我们需要知道如何衡量性能。
指标 | 全称 | 含义 | 目标值 |
FP | First Paint | 首次绘制 | < 1s |
FCP | First Contentful Paint | 首次内容绘制 | < 1.8s |
LCP | Largest Contentful Paint | 最大内容绘制 | < 2.5s |
TTI | Time to Interactive | 可交互时间 | < 3.8s |
CLS | Cumulative Layout Shift | 累积布局偏移 | < 0.1 |
FID | First Input Delay | 首次输入延迟 | < 100ms |
指标说明:
1. FP:浏览器第一次绘制像素的时间,用户开始看到内容
2. FCP:浏览器首次绘制文本、图像等有实际内容的时间
3. LCP:页面中最大可见内容渲染完成的时间(通常是大图或大段文本)
4. TTI:页面完全可交互的时间(用户可以点击、输入等)
5. CLS:页面元素在加载过程中意外移动的程度(比如图片加载后把文本往下推)
6. FID:用户首次与页面交互到浏览器响应的时间
如何查看这些指标?
使用 Chrome 开发者工具的 Lighthouse 面板:
7. 打开 Chrome 开发者工具(F12)
8. 切换到 Lighthouse 标签
9. 点击 Analyze page load
10. 等待分析完成,查看报告
1.3 资源加载优化
1.3.1 减少HTTP请求
每个资源文件(HTML、CSS、JS、图片)都需要一个HTTP请求,请求越多,加载越慢。
优化方法:
方法1:合并CSS和JS文件
<!-- 优化前:3个CSS文件 --> <link rel="stylesheet" href="reset.css"> <link rel="stylesheet" href="layout.css"> <link rel="stylesheet" href="theme.css"> <!-- 优化后:1个合并的CSS文件 --> <link rel="stylesheet" href="all.css"><!-- 优化前:3个JS文件 --> <script src="utils.js"></script> <script src="api.js"></script> <script src="app.js"></script> <!-- 优化后:1个合并的JS文件 --> <script src="bundle.js"></script>方法2:使用雪碧图(CSS Sprites)
将多个小图标合并成一张大图,通过 CSS background-position 显示不同部分。
/* 雪碧图示例 */ .icon { background-image: url('sprite.png'); background-repeat: no-repeat; display: inline-block; } .icon-home { width: 32px; height: 32px; background-position: 0 0; /* 显示第一个图标 */ } .icon-user { width: 32px; height: 32px; background-position: -32px 0; /* 显示第二个图标 */ } .icon-search { width: 32px; height: 32px; background-position: -64px 0; /* 显示第三个图标 */ }1.3.2 压缩资源
减小文件大小,加快传输速度。
HTML压缩:
<!-- 优化前 --> <!DOCTYPE html> <html> <head> <title>我的网页</title> <link rel="stylesheet" href="style.css"> </head> <body> <h1>欢迎来到我的网页</h1> <p>这是一段文字</p> </body> </html> <!-- 优化后:去除空格、换行、注释 --> <!DOCTYPE html><html><head><title>我的网页</title><link rel="stylesheet" href="style.css"></head><body><h1>欢迎来到我的网页</h1><p>这是一段文字</p></body></html>CSS压缩:
/* 优化前 */ body { margin: 0; padding: 0; font-family: Arial, sans-serif; } h1 { color: #333; font-size: 24px; } /* 优化后:去除空格、换行、注释 */ body{margin:0;padding:0;font-family:Arial,sans-serif}h1{color:#333;font-size:24px}JavaScript压缩:
// 优化前 function add(a, b) { return a + b; } const result = add(5, 3); console.log(result); // 优化后:变量名缩短、去除空格换行 function add(a,b){return a+b}const result=add(5,3);console.log(result);注意:手动压缩效率低,实际项目中使用构建工具自动完成(后续章节会讲)。
1.3.3 使用CDN加速
CDN(Content Delivery Network,内容分发网络)在全球部署服务器,用户从最近的服务器下载资源,加速访问。
示例:
<!-- 本地服务器加载 --> <script src="jquery-3.6.0.js"></script> <!-- 可能很慢 --> <!-- 使用CDN加载 --> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script> <!-- 更快 -->常用CDN:
• cdnjs:https://cdnjs.com/
• jsDelivr:https://www.jsdelivr.com/
• unpkg:https://unpkg.com/
1.3.4 图片优化
图片通常占网页资源的大头,优化图片能显著提升加载速度。
方法1:选择合适的图片格式
格式 | 优点 | 缺点 | 适用场景 |
JPEG | 压缩率高,文件小 | 不支持透明背景 | 照片、复杂图像 |
PNG | 支持透明背景,无损压缩 | 文件较大 | 图标、logo、简单图像 |
WebP | 压缩率比JPEG高30% | 兼容性稍差 | 现代浏览器首选 |
SVG | 矢量图,无限放大不失真 | 不适合复杂图像 | 图标、logo、插图 |
<!-- 推荐:使用WebP格式 --> <img src="photo.webp" alt="照片"> <!-- 备用:为不支持WebP的浏览器提供JPEG --> <picture> <source srcset="photo.webp" type="image/webp"> <img src="photo.jpg" alt="照片"> </picture>方法2:响应式图片
根据设备屏幕大小加载不同尺寸的图片。
<!-- 使用srcset和sizes --> <img src="small.jpg" srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1200w" sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px" alt="响应式图片" > <!-- 使用picture元素 --> <picture> <source media="(max-width: 600px)" srcset="small.jpg"> <source media="(max-width: 1000px)" srcset="medium.jpg"> <source media="(min-width: 1001px)" srcset="large.jpg"> <img src="fallback.jpg" alt="图片"> </picture>方法3:懒加载(Lazy Loading)
图片只在滚动到可视区域时才加载。
<!-- 原生懒加载(现代浏览器支持) --> <img src="placeholder.jpg" loading="lazy" alt="懒加载图片"> <!-- JavaScript实现懒加载(兼容性更好) --> <script> document.addEventListener('DOMContentLoaded', function() { const lazyImages = document.querySelectorAll('img[data-src]'); const lazyLoad = function() { const scrollTop = window.pageYOffset; lazyImages.forEach(function(img) { if (img.offsetTop < window.innerHeight + scrollTop) { img.src = img.dataset.src; img.classList.remove('lazy'); } }); }; window.addEventListener('scroll', lazyLoad); window.addEventListener('resize', lazyLoad); lazyLoad(); }); </script>1.4 代码执行优化
1.4.1 CSS选择器优化
CSS选择器越复杂,匹配越慢。
优化前:
/* 深度嵌套选择器,性能差 */ body header nav ul li a:hover { color: red; } /* 通配符选择器,性能差 */ * { margin: 0; padding: 0; } /* 属性选择器,性能差 */ input[type="text"] { border: 1px solid #ccc; }优化后:
/* 使用类名,性能好 */ .nav-link:hover { color: red; } /* 只重置需要的元素 */ body, h1, h2, h3, p, ul, li { margin: 0; padding: 0; } /* 使用类名 */ .input-text { border: 1px solid #ccc; }CSS选择器性能排名(从快到慢):
1. ID选择器(#id)
2. 类选择器(.class)
3. 标签选择器(div)
4. 相邻兄弟选择器(div + p)
5. 子选择器(div > p)
6. 后代选择器(div p)
7. 通配符选择器(*)
8. 属性选择器([type="text"])
9. 伪类选择器(:hover)
1.4.2 JavaScript性能优化
方法1:减少DOM操作
// 优化前:多次DOM操作,性能差 for (let i = 0; i < 1000; i++) { document.getElementById('list').innerHTML += '<li>项目' + i + '</li>'; } // 优化后:使用文档片段,减少重排重绘 const fragment = document.createDocumentFragment(); const list = document.getElementById('list'); for (let i = 0; i < 1000; i++) { const li = document.createElement('li'); li.textContent = '项目' + i; fragment.appendChild(li); } list.appendChild(fragment);方法2:使用事件委托
// 优化前:为每个元素添加事件监听器,内存占用大 document.querySelectorAll('.item').forEach(function(item) { item.addEventListener('click', function() { console.log('点击了:' + this.textContent); }); }); // 优化后:使用事件委托,只添加一个监听器 document.getElementById('list').addEventListener('click', function(e) { if (e.target.classList.contains('item')) { console.log('点击了:' + e.target.textContent); } });方法3:防抖和节流
防抖(Debounce):事件触发后,延迟n秒再执行,如果n秒内再次触发,则重新计时。
应用场景:搜索框输入、窗口resize事件
// 防抖函数 function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); }, delay); }; } // 使用示例:搜索框输入 const searchInput = document.getElementById('search'); const handleSearch = debounce(function(e) { console.log('搜索:' + e.target.value); // 发送AJAX请求 }, 500); searchInput.addEventListener('input', handleSearch);节流(Throttle):事件触发后,立即执行一次,然后在n秒内不再执行。
应用场景:滚动事件、鼠标移动事件
// 节流函数 function throttle(func, delay) { let lastTime = 0; return function(...args) { const now = Date.now(); if (now - lastTime >= delay) { func.apply(this, args); lastTime = now; } }; } // 使用示例:滚动事件 window.addEventListener('scroll', throttle(function() { console.log('页面滚动'); // 懒加载图片等操作 }, 200));对比:
用户连续输入:A -> B -> C -> D -> E 时间间隔:0 -> 100ms -> 200ms -> 300ms -> 400ms 延迟:500ms 防抖: - 输入A:500ms后执行 - 100ms后输入B:取消A的500ms,重新计时 - 200ms后输入C:取消B的500ms,重新计时 - 300ms后输入D:取消C的500ms,重新计时 - 400ms后输入E:取消D的500ms,重新计时 - 500ms后没有输入:执行E 节流: - 输入A:立即执行 - 100ms后输入B:忽略(距离上次执行不足200ms) - 200ms后输入C:执行 - 300ms后输入D:忽略 - 400ms后输入E:执行1.5 缓存策略
合理使用缓存可以避免重复下载资源,大幅提升访问速度。
1.5.1 浏览器缓存
浏览器缓存有两种方式:强缓存和协商缓存。
强缓存:
浏览器不向服务器请求,直接使用本地缓存。
实现方式:
# HTTP响应头 Cache-Control: max-age=3600 # 缓存3600秒(1小时) Expires: Wed, 22 Feb 2026 14:00:00 GMT # 过期时间(不推荐,优先级低于Cache-Control) 协商缓存:浏览器向服务器询问资源是否有更新,如果有更新则下载新资源,否则使用本地缓存。
实现方式:
# HTTP响应头 ETag: "abc123" # 文件的唯一标识 Last-Modified: Wed, 22 Feb 2026 10:00:00 GMT # 文件最后修改时间 # HTTP请求头(浏览器自动发送) If-None-Match: "abc123" # 上次响应的ETag If-Modified-Since: Wed, 22 Feb 2026 10:00:00 GMT # 上次响应的Last-Modified # HTTP响应(如果资源未修改) 304 Not Modified # 浏览器使用本地缓存 # HTTP响应(如果资源已修改) 200 OK # 返回新资源缓存策略对比:
策略 | 优点 | 缺点 | 适用场景 |
强缓存 | 不请求服务器,速度最快 | 更新不及时 | 不常变化的静态资源(CSS、JS、图片) |
协商缓存 | 能及时获取更新 | 需要请求服务器 | 可能变化的资源(HTML、API数据) |
1.5.2 LocalStorage缓存
使用LocalStorage缓存API数据,减少重复请求。
// 缓存API数据 async function fetchUser(id) { const cacheKey = 'user_' + id; const cachedData = localStorage.getItem(cacheKey); const cacheTime = localStorage.getItem(cacheKey + '_time'); // 检查缓存是否存在且未过期(1小时) if (cachedData && cacheTime && (Date.now() - cacheTime < 3600000)) { console.log('使用缓存数据'); return JSON.parse(cachedData); } // 请求新数据 console.log('请求新数据'); const response = await fetch('https://api.example.com/users/' + id); const data = await response.json(); // 缓存数据 localStorage.setItem(cacheKey, JSON.stringify(data)); localStorage.setItem(cacheKey + '_time', Date.now()); return data; } // 使用 fetchUser(123).then(user => { console.log(user); });1.5.3 Service Worker缓存
Service Worker是浏览器提供的高级缓存技术,可以实现离线访问。
特点:
• 独立于主线程运行
• 可以拦截网络请求
• 可以缓存资源
• 支持离线访问
示例:
// 注册Service Worker if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js') .then(registration => { console.log('Service Worker注册成功'); }) .catch(error => { console.log('Service Worker注册失败:', error); }); } // javascript // sw.js const CACHE_NAME = 'my-cache-v1'; const urlsToCache = [ '/', '/index.html', '/styles.css', '/script.js', '/image.jpg' ]; // 安装Service Worker,缓存资源 self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => { return cache.addAll(urlsToCache); }) ); }); // 拦截请求,从缓存返回 self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request) .then(response => { // 如果缓存中有,直接返回 if (response) { return response; } // 否则请求网络 return fetch(event.request).then(response => { // 缓存新请求 const responseToCache = response.clone(); caches.open(CACHE_NAME) .then(cache => { cache.put(event.request, responseToCache); }); return response; }); }) ); }); // 激活Service Worker,清理旧缓存 self.addEventListener('activate', event => { event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(cacheName => { if (cacheName !== CACHE_NAME) { return caches.delete(cacheName); } }) ); }) ); });第二部分:代码质量提升
写能运行的代码不难,写好代码需要技巧和习惯。
2.1 模块化编程
2.1.1 什么是模块化?
模块化是将代码拆分成独立、可复用的模块,每个模块负责特定的功能。
好处:
• 提高代码可维护性
• 便于团队协作
• 避免命名冲突
• 提高代码复用性
2.1.2 ES6模块化
导出(export):
// utils.js // 命名导出 export function add(a, b) { return a + b; } export function multiply(a, b) { return a * b; } export const PI = 3.14159; // 默认导出 export default function subtract(a, b) { return a - b; }导入(import):
// main.js // 导入默认导出 import subtract from './utils.js'; // 导入命名导出 import { add, multiply, PI } from './utils.js'; // 重命名导入 import { add as addition } from './utils.js'; // 导入所有 import * as utils from './utils.js'; // 使用 console.log(add(2, 3)); // 5 console.log(addition(2, 3)); // 5 console.log(multiply(2, 3)); // 6 console.log(PI); // 3.14159 console.log(subtract(5, 3)); // 2 console.log(utils.add(2, 3)); // 5在HTML中使用模块:
<script type="module" src="main.js"></script>2.1.3 模块化实战:待办事项应用
让我们用模块化重构待办事项应用。
文件结构:
project/ ├── index.html ├── css/ │ └── style.css ├── js/ │ ├── main.js # 入口文件 │ ├── task.js # Task类 │ ├── taskService.js # TaskService类 │ └── taskView.js # TaskView类task.js:
// task.js export class Task { constructor(id, text, completed = false) { this.id = id; this.text = text; this.completed = completed; this.createdAt = new Date(); } }taskService.js:
// taskService.js import { Task } from './task.js'; export class TaskService { constructor() { this.tasks = []; this.loadTasks(); } loadTasks() { const storedTasks = localStorage.getItem('tasks'); if (storedTasks) { this.tasks = JSON.parse(storedTasks).map(task => { const newTask = new Task(task.id, task.text, task.completed); newTask.createdAt = new Date(task.createdAt); return newTask; }); } } saveTasks() { localStorage.setItem('tasks', JSON.stringify(this.tasks)); } addTask(text) { const task = new Task(Date.now().toString(), text); this.tasks.push(task); this.saveTasks(); return task; } toggleTask(id) { const task = this.tasks.find(t => t.id === id); if (task) { task.completed = !task.completed; this.saveTasks(); return task; } return null; } deleteTask(id) { const index = this.tasks.findIndex(t => t.id === id); if (index !== -1) { this.tasks.splice(index, 1); this.saveTasks(); return true; } return false; } updateTask(id, newText) { const task = this.tasks.find(t => t.id === id); if (task) { task.text = newText; this.saveTasks(); return task; } return null; } getStats() { const total = this.tasks.length; const completed = this.tasks.filter(t => t.completed).length; return { total, completed, active: total - completed }; } filterTasks(filter) { switch (filter) { case 'active': return this.tasks.filter(t => !t.completed); case 'completed': return this.tasks.filter(t => t.completed); default: return [...this.tasks]; } } }taskView.js:
// taskView.js import { TaskService } from './taskService.js'; export class TaskView { constructor(taskService) { this.taskService = taskService; this.taskForm = document.getElementById('taskForm'); this.taskInput = document.getElementById('taskInput'); this.taskList = document.getElementById('taskList'); this.taskCount = document.getElementById('taskCount'); this.filterButtons = document.querySelectorAll('.filter-btn'); this.currentFilter = 'all'; this.initEventListeners(); this.renderTasks(); this.updateStats(); } initEventListeners() { this.taskForm.addEventListener('submit', (e) => { e.preventDefault(); this.handleAddTask(); }); this.filterButtons.forEach(btn => { btn.addEventListener('click', () => { this.filterButtons.forEach(b => b.classList.remove('active')); btn.classList.add('active'); this.currentFilter = btn.dataset.filter; this.renderTasks(); }); }); this.taskList.addEventListener('click', (e) => { const taskItem = e.target.closest('.task-item'); if (!taskItem) return; const taskId = taskItem.dataset.id; if (e.target.classList.contains('task-checkbox')) { this.handleToggleTask(taskId); } else if (e.target.classList.contains('delete-btn')) { this.handleDeleteTask(taskId); } else if (e.target.classList.contains('edit-btn')) { this.handleEditTask(taskId); } }); } handleAddTask() { const text = this.taskInput.value.trim(); if (text) { const task = this.taskService.addTask(text); this.renderTask(task); this.taskInput.value = ''; this.updateStats(); } } handleToggleTask(taskId) { const task = this.taskService.toggleTask(taskId); if (task) { const taskElement = document.querySelector(`.task-item[data-id="${taskId}"]`); if (taskElement) { taskElement.classList.toggle('task-completed', task.completed); taskElement.querySelector('.task-checkbox').checked = task.completed; this.updateStats(); } } } handleDeleteTask(taskId) { if (confirm('确定要删除这个任务吗?')) { const success = this.taskService.deleteTask(taskId); if (success) { const taskElement = document.querySelector(`.task-item[data-id="${taskId}"]`); if (taskElement) { taskElement.remove(); this.updateStats(); } } } } handleEditTask(taskId) { const task = this.taskService.tasks.find(t => t.id === taskId); if (task) { const newText = prompt('编辑任务', task.text); if (newText !== null && newText.trim() !== '') { const updatedTask = this.taskService.updateTask(taskId, newText.trim()); if (updatedTask) { const taskElement = document.querySelector(`.task-item[data-id="${taskId}"]`); if (taskElement) { taskElement.querySelector('.task-text').textContent = updatedTask.text; } } } } } renderTask(task) { const li = document.createElement('li'); li.className = `task-item ${task.completed ? 'task-completed' : ''}`; li.dataset.id = task.id; li.innerHTML = ` <input type="checkbox" ${task.completed ? 'checked' : ''}> <span>${task.text}</span> <div> <button>✏️</button> <button>��️</button> </div> `; this.taskList.appendChild(li); } renderTasks() { this.taskList.innerHTML = ''; const filteredTasks = this.taskService.filterTasks(this.currentFilter); if (filteredTasks.length === 0) { const emptyMessage = document.createElement('li'); emptyMessage.textContent = '没有任务'; emptyMessage.style.textAlign = 'center'; emptyMessage.style.padding = '20px'; emptyMessage.style.color = '#777'; this.taskList.appendChild(emptyMessage); return; } filteredTasks.forEach(task => this.renderTask(task)); } updateStats() { const stats = this.taskService.getStats(); this.taskCount.textContent = `任务总数:${stats.total} | 已完成:${stats.completed} | 未完成:${stats.active}`; } }main.js:
// main.js import { TaskService } from './js/taskService.js'; import { TaskView } from './js/taskView.js'; document.addEventListener('DOMContentLoaded', () => { const taskService = new TaskService(); new TaskView(taskService); });index.html:
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>待办事项应用(模块化版本)</title> <link rel="stylesheet" href="css/style.css"> </head> <body> <div> <header> <h1>待办事项</h1> <p>任务总数:0 | 已完成:0 | 未完成:0</p> </header> <form> <input type="text" placeholder="添加新任务..." required> <button type="submit">添加</button> </form> <div> <button>全部</button> <button>未完成</button> <button>已完成</button> </div> <ul></ul> </div> <script type="module" src="js/main.js"></script> </body> </html>模块化的优势:
1. 职责分离:每个模块只负责一个功能
2. 易于维护:修改某个功能只需修改对应的模块
3. 易于测试:可以单独测试每个模块
4. 代码复用:模块可以在不同项目中复用
2.2 设计模式
设计模式是解决常见问题的经典方案,掌握设计模式能写出更优雅的代码。
2.2.1 单例模式(Singleton)
确保一个类只有一个实例,并提供一个全局访问点。
应用场景:全局配置、数据库连接、日志管理器
// 单例模式示例 class Config { constructor() { if (Config.instance) { return Config.instance; } this.apiUrl = 'https://api.example.com'; this.timeout = 5000; this.debug = true; Config.instance = this; } static getInstance() { if (!Config.instance) { Config.instance = new Config(); } return Config.instance; } } // 使用 const config1 = Config.getInstance(); const config2 = Config.getInstance(); console.log(config1 === config2); // true,是同一个实例 console.log(config1.apiUrl); // https://api.example.com2.2.2 观察者模式(Observer)
当一个对象的状态发生变化时,所有依赖它的对象都会得到通知。
应用场景:事件处理、数据绑定、发布订阅
// 观察者模式示例 class EventEmitter { constructor() { this.events = {}; } // 订阅事件 on(eventName, callback) { if (!this.events[eventName]) { this.events[eventName] = []; } this.events[eventName].push(callback); } // 触发事件 emit(eventName, data) { if (this.events[eventName]) { this.events[eventName].forEach(callback => { callback(data); }); } } // 取消订阅 off(eventName, callback) { if (this.events[eventName]) { this.events[eventName] = this.events[eventName].filter(cb => cb !== callback); } } } // 使用 const emitter = new EventEmitter(); // 订阅事件 emitter.on('user-login', (user) => { console.log('用户登录:', user.name); }); emitter.on('user-login', (user) => { console.log('发送欢迎邮件给:', user.email); }); // 触发事件 emitter.emit('user-login', { name: '张三', email: '[email protected]' }); // 输出: // 用户登录:张三 // 发送欢迎邮件给:[email protected]2.2.3 工厂模式(Factory)
定义一个创建对象的接口,让子类决定实例化哪个类。
应用场景:创建复杂对象、数据库连接、HTTP请求
// 工厂模式示例 class Button { constructor(text) { this.text = text; } render() { throw new Error('子类必须实现render方法'); } } class PrimaryButton extends Button { render() { return `<button>${this.text}</button>`; } } class SecondaryButton extends Button { render() { return `<button>${this.text}</button>`; } } class DangerButton extends Button { render() { return `<button>${this.text}</button>`; } } // 工厂函数 function createButton(type, text) { switch (type) { case 'primary': return new PrimaryButton(text); case 'secondary': return new SecondaryButton(text); case 'danger': return new DangerButton(text); default: throw new Error('未知的按钮类型:' + type); } } // 使用 const primaryBtn = createButton('primary', '提交'); const secondaryBtn = createButton('secondary', '取消'); const dangerBtn = createButton('danger', '删除'); console.log(primaryBtn.render()); // <button>提交</button> console.log(secondaryBtn.render()); // <button>取消</button> console.log(dangerBtn.render()); // <button>删除</button>2.2.4 策略模式(Strategy)
定义一系列算法,把它们封装起来,并使它们可以互相替换。
应用场景:表单验证、排序算法、支付方式
// 策略模式示例:表单验证 // 策略1:必填验证 const required = { validate: (value) => { return value.trim() !== ''; }, message: '此字段为必填项' }; // 策略2:邮箱验证 const email = { validate: (value) => { const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return regex.test(value); }, message: '请输入有效的邮箱地址' }; // 策略3:最小长度验证 const minLength = (min) => ({ validate: (value) => { return value.length >= min; }, message: `最少需要${min}个字符` }); // 策略4:手机号验证 const phone = { validate: (value) => { const regex = /^1[3-9]\d{9}$/; return regex.test(value); }, message: '请输入有效的手机号' }); // 验证器 class Validator { constructor() { this.rules = []; } addRule(field, rule) { this.rules.push({ field, rule }); } validate(data) { const errors = {}; for (const { field, rule } of this.rules) { if (!rule.validate(data[field])) { if (!errors[field]) { errors[field] = []; } errors[field].push(rule.message); } } return { isValid: Object.keys(errors).length === 0, errors }; } } // 使用 const validator = new Validator(); // 添加验证规则 validator.addRule('username', required); validator.addRule('username', minLength(3)); validator.addRule('email', required); validator.addRule('email', email); validator.addRule('phone', phone); // 验证数据 const formData = { username: 'ab', email: 'invalid-email', phone: '12345' }; const result = validator.validate(formData); if (!result.isValid) { console.log('验证失败:'); for (const field in result.errors) { console.log(`${field}: ${result.errors[field].join(', ')}`); } } // 输出: // 验证失败: // username: 此字段为必填项, 最少需要3个字符 // email: 请输入有效的邮箱地址 // phone: 请输入有效的手机号2.3 代码规范
2.3.1 命名规范
好的命名让代码自解释,无需注释。
变量命名:
// 坏的命名 const a = 10; const b = 20; const c = a + b; // 好的命名 const price = 10; const quantity = 20; const total = price * quantity;// 坏的命名 const d = new Date(); // 好的命名 const currentDate = new Date(); const lastLoginDate = new Date();函数命名:
// 坏的命名 function calc(x, y) { return x + y; } // 好的命名:动词+名词 function calculateTotal(price, quantity) { return price * quantity; } function getUserById(userId) { // ... } function validateEmail(email) { // ... } function fetchUserData() { // ... }类命名:
// 坏的命名 class u { constructor(name) { this.n = name; } } // 好的命名:大驼峰命名法(PascalCase) class User { constructor(name) { this.name = name; } } class TaskManager { // ... } class PaymentService { // ... }布尔值命名:
// 坏的命名 let flag = true; let check = false; let status = 1; // 好的命名:使用is、has、can、should等前缀 let isUserLoggedIn = true; let hasPermission = false; let canEdit = true; let shouldDelete = false;常量命名:
// 坏的命名 const max = 100; const timeout = 5000; // 好的命名:全大写+下划线 const MAX_RETRY_COUNT = 3; const API_TIMEOUT = 5000; const DEFAULT_PAGE_SIZE = 20;2.3.2 注释规范
注释应该解释"为什么",而不是"是什么"。
// 坏的注释:重复代码 // 计算总价 const total = price * quantity; // 好的注释:解释原因 // 使用Math.round避免浮点数精度问题 const total = Math.round(price * quantity);// 坏的注释:废话 // 定义用户数组 const users = []; // 好的注释:说明限制条件 // 用户列表最多存储1000条记录,超过后会自动删除最早的记录 const users = [];// 函数注释:使用JSDoc格式 /** * 计算两个数的和 * @param {number} a - 第一个数 * @param {number} b - 第二个数 * @returns {number} 两数之和 * @example * add(2, 3) // 5 */ function add(a, b) { return a + b; }2.3.3 代码格式
使用代码格式化工具保证代码风格统一。
推荐工具:
• Prettier:代码格式化工具
• ESLint:代码检查工具
安装:
npm install --save-dev prettier eslint配置文件 .prettierrc:
{ "semi": true, "singleQuote": true, "tabWidth": 2, "trailingComma": "es5", "printWidth": 80 }使用:
# 格式化所有文件 npx prettier --write "**/*.{js,css,html}" # 检查代码格式 npx prettier --check "**/*.{js,css,html}" # ESLint检查 npx eslint "**/*.js" # ESLint自动修复 npx eslint "**/*.js" --fix第三部分:开发工具与工程化
现代前端开发离不开工具链,合理使用工具能大幅提升开发效率。
3.1 包管理器
3.1.1 npm(Node Package Manager)
npm是Node.js的包管理器,用于安装和管理JavaScript依赖包。
初始化项目:
npm init -y这会创建一个 package.json 文件:
{ "name": "my-project", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }安装依赖包:
# 安装生产环境依赖 npm install lodash npm install axios # 一次性安装多个包 npm install lodash axios # 安装开发环境依赖 npm install --save-dev prettier eslint # 简写 npm i lodash npm i -D prettierpackage.json变化:
{ "dependencies": { "axios": "^1.6.0", "lodash": "^4.17.21" }, "devDependencies": { "eslint": "^8.50.0", "prettier": "^3.0.0" } }使用依赖包:
// 引入lodash const _ = require('lodash'); // 使用lodash const arr = [1, 2, 3, 4, 5]; const sum = _.sum(arr); console.log(sum); // 15 // 在模块化项目中使用ES6 import import _ from 'lodash';常用命令:
# 查看已安装的包 npm list # 查看全局安装的包 npm list -g # 更新依赖包 npm update # 卸载包 npm uninstall lodash # 查看包信息 npm info lodash # 搜索包 npm search date-fns3.1.2 yarn和pnpm
yarn和pnpm是npm的替代方案,速度更快。
安装yarn:
npm install -g yarn使用yarn:
# 初始化项目 yarn init -y # 安装依赖 yarn add lodash yarn add --dev prettier # 删除依赖 yarn remove lodash # 更新依赖 yarn upgrade # 安装所有依赖 yarn install安装pnpm:
npm install -g pnpm使用pnpm:
# 初始化项目 pnpm init # 安装依赖 pnpm add lodash pnpm add -D prettier # 删除依赖 pnpm remove lodash # 安装所有依赖 pnpm installnpm、yarn、pnpm对比:
特性 | npm | yarn | pnpm |
安装速度 | 慢 | 快 | 最快 |
磁盘占用 | 大 | 中 | 小 |
兼容性 | 最好 | 好 | 较好 |
生态 | 最完善 | 完善 | 快速发展 |
3.2 版本控制:Git
Git是分布式版本控制系统,是团队协作的必备工具。
3.2.1 基本概念
• 仓库(Repository):存储项目文件和版本历史的地方
• 提交(Commit):保存项目的一个快照
• 分支(Branch):独立的开发线
• 合并(Merge):将分支合并到主分支
3.2.2 常用命令
初始化仓库:
git init添加文件到暂存区:
# 添加所有文件 git add . # 添加指定文件 git add index.html # 查看暂存区状态 git status提交到本地仓库:
git commit -m "feat: 添加待办事项功能"提交信息规范:
类型 | 说明 | 示例 |
feat | 新功能 | feat: 添加用户登录功能 |
fix | 修复bug | fix: 修复登录页面的样式问题 |
docs | 文档更新 | docs: 更新README文档 |
style | 代码格式调整 | style: 调整代码缩进 |
refactor | 重构代码 | refactor: 重构用户模块 |
test | 测试相关 | test: 添加单元测试 |
chore | 构建/工具 | chore: 更新依赖包 |
查看提交历史:
# 查看提交历史 git log # 查看简洁的提交历史 git log --oneline # 查看某个文件的修改历史 git log --follow index.html创建和切换分支:
# 创建分支 git branch feature/login # 切换分支 git checkout feature/login # 创建并切换分支(简写) git checkout -b feature/login # 查看所有分支 git branch # 删除分支 git branch -d feature/login合并分支:
# 切换到主分支 git checkout main # 合并feature分支 git merge feature/login # 删除已合并的分支 git branch -d feature/login推送到远程仓库:
# 关联远程仓库 git remote add origin https://github.com/username/repo.git # 推送到远程仓库 git push origin main # 推送所有分支 git push --all origin # 推送标签 git push --tags从远程仓库拉取:
# 拉取远程更新 git pull origin main # 获取远程更新但不合并 git fetch origin main # 查看远程仓库 git remote -v3.2.3 .gitignore文件
.gitignore文件指定哪些文件不需要被Git跟踪。
示例 .gitignore:
# 依赖 node_modules/ package-lock.json # 构建产物 dist/ build/ *.min.js # 环境变量 .env .env.local # 编辑器 .vscode/ .idea/ *.swp # 操作系统 .DS_Store Thumbs.db # 日志 *.log # 临时文件 tmp/ temp/3.3 构建工具
构建工具用于自动化处理代码转换、压缩、打包等任务。
3.3.1 Webpack
Webpack是当前最流行的模块打包工具。
安装:
npm install --save-dev webpack webpack-cli基本配置 webpack.config.js:
const path = require('path'); module.exports = { // 入口文件 entry: './src/index.js', // 输出配置 output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, // 模式 mode: 'development', // 加载器 module: { rules: [ // 处理CSS { test: /\.css$/, use: ['style-loader', 'css-loader'] }, // 处理图片 { test: /\.(png|jpg|jpeg|gif|svg)$/, type: 'asset/resource' }, // 转换ES6+ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] }, // 插件 plugins: [ // HTML模板插件 new HtmlWebpackPlugin({ template: './src/index.html', filename: 'index.html' }) ], // 开发服务器 devServer: { static: { directory: path.join(__dirname, 'dist') }, port: 3000, open: true } };安装必要的依赖:
npm install --save-dev html-webpack-plugin style-loader css-loader babel-loader @babel/core @babel/preset-env webpack-dev-server package.json添加脚本:{ "scripts": { "dev": "webpack serve --mode development", "build": "webpack --mode production" } }使用:
# 开发模式(启动开发服务器) npm run dev # 生产模式(打包) npm run build3.3.2 Vite
Vite是新一代构建工具,速度更快。
安装:
npm create vite@latest my-project cd my-project npm install开发:
npm run dev构建:
npm run buildVite vs Webpack对比:
特性 | Webpack | Vite |
启动速度 | 慢(需要打包) | 快(按需编译) |
热更新 | 较慢 | 极快 |
配置复杂度 | 高 | 低 |
生态 | 完善 | 快速发展 |
适用场景 | 大型项目 | 现代项目 |
3.4 调试技巧
3.4.1 Chrome开发者工具
Elements面板:查看和修改HTML、CSS
Console面板:查看日志、执行JavaScript
// console.log:输出普通信息 console.log('普通日志'); console.log('用户信息:', { name: '张三', age: 25 }); // console.error:输出错误信息 console.error('发生错误:', error); // console.warn:输出警告信息 console.warn('注意:这个功能即将废弃'); // console.table:以表格形式输出对象数组 const users = [ { name: '张三', age: 25 }, { name: '李四', age: 30 } ]; console.table(users); // console.time / console.timeEnd:测量代码执行时间 console.time('耗时'); // 执行一些代码 console.timeEnd('耗时'); // console.group / console.groupEnd:分组输出 console.group('用户信息'); console.log('姓名:张三'); console.log('年龄:25'); console.groupEnd();Network面板:查看网络请求
Application面板:查看LocalStorage、SessionStorage、Cookie等
Performance面板:分析页面性能
3.4.2 断点调试
在代码中设置断点:
function calculateSum(a, b) { debugger; // 在这里设置断点,代码执行会暂停 const sum = a + b; return sum; } calculateSum(10, 20);在开发者工具中设置断点:
1. 打开Sources面板
2. 找到要调试的文件
3. 点击行号设置断点
4. 刷新页面或触发代码执行
5. 使用调试控制按钮(继续、单步进入、单步跳出、单步跳过)
本篇知识总结
性能优化篇
知识点 | 内容 |
性能指标 | FP、FCP、LCP、TTI、CLS、FID |
资源优化 | 合并文件、压缩、CDN、图片优化、懒加载 |
代码优化 | CSS选择器优化、DOM操作优化、事件委托、防抖节流 |
缓存策略 | 浏览器缓存、LocalStorage、Service Worker |
代码质量篇
知识点 | 内容 |
模块化 | ES6模块、职责分离、文件组织 |
设计模式 | 单例模式、观察者模式、工厂模式、策略模式 |
代码规范 | 命名规范、注释规范、代码格式(Prettier、ESLint) |
工程化篇
知识点 | 内容 |
包管理器 | npm、yarn、pnpm |
版本控制 | Git基本操作、分支管理、.gitignore |
构建工具 | Webpack、Vite |
调试技巧 | Chrome开发者工具、断点调试 |
课后练习
练习1:性能优化
找一个简单的网页,完成以下优化:
1. 合并CSS和JS文件
2. 压缩所有资源文件
3. 为图片添加懒加载
4. 使用CDN加载jQuery或Vue等库
5. 使用Lighthouse测试优化效果
练习2:模块化重构
将之前写的待办事项应用重构为模块化结构:
1. 拆分成Task、TaskService、TaskView三个模块
2. 使用ES6模块化语法
3. 使用Webpack或Vite构建项目
练习3:Git版本控制
为你的项目添加Git版本控制:
1. 初始化Git仓库
2. 创建.gitignore文件
3. 提交代码到本地仓库
4. 在GitHub上创建远程仓库
5. 推送代码到远程仓库
练习4:使用构建工具
使用Vite创建一个新项目:
1. 安装Vite
2. 创建项目
3. 配置项目
4. 开发和构建
练习5:性能分析
使用Chrome开发者工具分析一个网站:
1. 打开Lighthouse,生成性能报告
2. 分析Network面板,找出加载慢的资源
3. 分析Performance面板,找出性能瓶颈
4. 提出优化建议
结语
进阶(一)我们学习了前端性能优化、代码质量提升和工程化实践。这些都是专业前端工程师必备的技能。
如果说基础系列教你"如何写代码",那么进阶系列教你"如何成为专业开发者"。
下一篇进阶文章,我们将深入学习:
• 前端框架(Vue/React)
• TypeScript基础
• 前端测试
• 部署与CI/CD
版权声明:
本文为原创文章,转载请注明出处。未经作者许可,禁止用于商业用途。