前言
基础系列教你"怎么写代码",进阶系列教你"如何写好代码"。
如果用烹饪来比喻:
• 基础系列:教你认识食材,掌握基本的刀工和火候
• 进阶系列:教你食材搭配、营养搭配、摆盘艺术、厨房管理
这篇进阶文章,我们将聚焦三个核心方向:
-
性能优化 :让页面加载更快、运行更流畅
-
代码质量 :让代码更易维护、更健壮
-
工程化实践 :用现代工具提升开发效率
准备好了吗?让我们从"会写代码"迈向"专业开发"!
第一部分:前端性能优化
性能优化是前端工程师的核心竞争力。一个页面,功能再强大,如果加载慢、操作卡顿,用户也会流失。
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 |
指标说明 :
-
FP :浏览器第一次绘制像素的时间,用户开始看到内容
-
FCP :浏览器首次绘制文本、图像等有实际内容的时间
-
LCP :页面中最大可见内容渲染完成的时间(通常是大图或大段文 本)
-
TTI :页面完全可交互的时间(用户可以点击、输入等)
-
CLS :页面元素在加载过程中意外移动的程度(比如图片加载后把文 本往下推)
-
FID :用户首次与页面交互到浏览器响应的时间
如何查看这些指标?
使用 Chrome 开发者工具的 Lighthouse 面板:
-
打开 Chrome 开发者工具(F12)
-
切换到 Lighthouse 标签
-
点击 Analyze page load
-
等待分析完成,查看报告
1.3 资源加载优化
1.3.1 减少HTTP请求
每个资源文件(HTML、CSS、JS、图片)都需要一个HTTP请求,请求越多,加载越慢。
优化方法 :
方法1:合并CSS和JS文件
html
<!-- 优化前: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">
html
<!-- 优化前: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 显示不同部 分。
css
/* 雪碧图示例 */
.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压缩 :
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压缩 :
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压缩 :
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,内容分发网络)在全球部署服务器,用户从最近的服务器下载资源,加速访问。
示例 :
html
<!-- 本地服务器加载 -->
<script src="jquery-3.6.0.js"></script> <!-- 可能很慢 -->
<!-- 使用CDN加载 -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/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、插图 |
html
<!-- 推荐:使用WebP格式 -->
<img src="photo.webp" alt="照片">
<!-- 备用:为不支持WebP的浏览器提供JPEG -->
<picture>
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="照片">
</picture>
方法2:响应式图片
根据设备屏幕大小加载不同尺寸的图片。
html
<!-- 使用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)
图片只在滚动到可视区域时才加载。
html
<!-- 原生懒加载(现代浏览器支持) -->
<img src="placeholder.jpg" data-src="real-image.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选择器越复杂,匹配越慢。
优化前 :
css
/* 深度嵌套选择器,性能差 */
body header nav ul li a:hover {
color: red;
}
/* 通配符选择器,性能差 */
* {
margin: 0;
padding: 0;
}
/* 属性选择器,性能差 */
input[type="text"] {
border: 1px solid #ccc;
}
优化后 :
css
/* 使用类名,性能好 */
.nav-link:hover {
color: red;
}
/* 只重置需要的元素 */
body, h1, h2, h3, p, ul, li {
margin: 0;
padding: 0;
}
/* 使用类名 */
.input-text {
border: 1px solid #ccc;
}
CSS选择器性能排名(从快到慢) :
-
ID选择器(#id)
-
类选择器(.class)
-
标签选择器(div)
-
相邻兄弟选择器(div + p)
-
子选择器(div > p)
-
后代选择器(div p)
-
通配符选择器(*)
-
属性选择器([type="text"])
-
伪类选择器(:hover)
1.4.2 JavaScript性能优化
方法1:减少DOM操作
javascript
// 优化前:多次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:使用事件委托
javascript
// 优化前:为每个元素添加事件监听器,内存占用大
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事件
javascript
// 防抖函数
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秒内不再执行。
应用场景 :滚动事件、鼠标移动事件
javascript
// 节流函数
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));
对比 :
javascript
用户连续输入: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 浏览器缓存
浏览器缓存有两种方式:强缓存和协商缓存。
强缓存 :
浏览器不向服务器请求,直接使用本地缓存。
实现方式 :
html
# HTTP响应头
Cache-Control: max-age=3600 # 缓存3600秒(1小时)
Expires: Wed, 22 Feb 2026 14:00:00 GMT # 过期时间(不推荐,优先级低于Cache-Control)
协商缓存:
浏览器向服务器询问资源是否有更新,如果有更新则下载新资源,否则使用本 地缓存。
实现方式 :
html
# 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数据,减少重复请求。
javascript
// 缓存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是浏览器提供的高级缓存技术,可以实现离线访问。
特点 :
• 独立于主线程运行
• 可以拦截网络请求
• 可以缓存资源
• 支持离线访问
示例 :
javascript
// 注册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) :
javascript
// 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) :
javascript
// 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中使用模块 :
html
<script type="module" src="main.js"></script>
2.1.3 模块化实战:待办事项应用
让我们用模块化重构待办事项应用。
文件结构 :
html
project/
├── index.html
├── css/
│ └── style.css
├── js/
│ ├── main.js # 入口文件
│ ├── task.js # Task类
│ ├── taskService.js # TaskService类
│ └── taskView.js # TaskView类
task.js :
javascript
// 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 :
javascript
// 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 :
javascript
// 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" class="task-checkbox" ${task.completed ? 'checked' : ''}>
<span class="task-text">${task.text}</span>
<div class="task-actions">
<button class="task-btn edit-btn">✏️</button>
<button class="task-btn delete-btn">��️</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 :
javascript
// 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 :
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 class="container">
<header>
<h1>待办事项</h1>
<p id="taskCount">任务总数:0 | 已完成:0 | 未完成:0</p>
</header>
<form id="taskForm">
<input type="text" id="taskInput" placeholder="添加新任务..." required>
<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="taskList" class="task-list"></ul>
</div>
<script type="module" src="js/main.js"></script>
</body>
</html>
模块化的优势 :
-
职责分离 :每个模块只负责一个功能
-
易于维护 :修改某个功能只需修改对应的模块
-
易于测试 :可以单独测试每个模块
-
代码复用 :模块可以在不同项目中复用
2.2 设计模式
设计模式是解决常见问题的经典方案,掌握设计模式能写出更优雅的代码。
2.2.1 单例模式(Singleton)
确保一个类只有一个实例,并提供一个全局访问点。
应用场景 :全局配置、数据库连接、日志管理器
javascript
// 单例模式示例
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.com
2.2.2 观察者模式(Observer)
当一个对象的状态发生变化时,所有依赖它的对象都会得到通知。
应用场景 :事件处理、数据绑定、发布订阅
javascript
// 观察者模式示例
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: 'zhangsan@example.com' });
// 输出:
// 用户登录:张三
// 发送欢迎邮件给:zhangsan@example.com
2.2.3 工厂模式(Factory)
定义一个创建对象的接口,让子类决定实例化哪个类。
应用场景 :创建复杂对象、数据库连接、HTTP请求
javascript
// 工厂模式示例
class Button {
constructor(text) {
this.text = text;
}
render() {
throw new Error('子类必须实现render方法');
}
}
class PrimaryButton extends Button {
render() {
return `<button class="btn btn-primary">${this.text}</button>`;
}
}
class SecondaryButton extends Button {
render() {
return `<button class="btn btn-secondary">${this.text}</button>`;
}
}
class DangerButton extends Button {
render() {
return `<button class="btn btn-danger">${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 class="btn btn-primary">提交</button>
console.log(secondaryBtn.render()); // <button class="btn btn-secondary">取消</button>
console.log(dangerBtn.render()); // <button class="btn btn-danger">删除</button>
2.2.4 策略模式(Strategy)
定义一系列算法,把它们封装起来,并使它们可以互相替换。
应用场景 :表单验证、排序算法、支付方式
javascript
// 策略模式示例:表单验证
// 策略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 命名规范
好的命名让代码自解释,无需注释。
变量命名 :
javascript
// 坏的命名
const a = 10;
const b = 20;
const c = a + b;
// 好的命名
const price = 10;
const quantity = 20;
const total = price * quantity;
javascript
// 坏的命名
const d = new Date();
// 好的命名
const currentDate = new Date();
const lastLoginDate = new Date();
函数命名 :
javascript
// 坏的命名
function calc(x, y) {
return x + y;
}
// 好的命名:动词+名词
function calculateTotal(price, quantity) {
return price * quantity;
}
function getUserById(userId) {
// ...
}
function validateEmail(email) {
// ...
}
function fetchUserData() {
// ...
}
类命名 :
javascript
// 坏的命名
class u {
constructor(name) {
this.n = name;
}
}
// 好的命名:大驼峰命名法(PascalCase)
class User {
constructor(name) {
this.name = name;
}
}
class TaskManager {
// ...
}
class PaymentService {
// ...
}
布尔值命名 :
javascript
// 坏的命名
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;
常量命名 :
javascript
// 坏的命名
const max = 100;
const timeout = 5000;
// 好的命名:全大写+下划线
const MAX_RETRY_COUNT = 3;
const API_TIMEOUT = 5000;
const DEFAULT_PAGE_SIZE = 20;
2.3.2 注释规范
注释应该解释"为什么",而不是"是什么"。
javascript
// 坏的注释:重复代码
// 计算总价
const total = price * quantity;
// 好的注释:解释原因
// 使用Math.round避免浮点数精度问题
const total = Math.round(price * quantity);
javascript
// 坏的注释:废话
// 定义用户数组
const users = [];
// 好的注释:说明限制条件
// 用户列表最多存储1000条记录,超过后会自动删除最早的记录
const users = [];
javascript
// 函数注释:使用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 :代码检查工具
安装 :
bash
npm install --save-dev prettier eslint
配置文件 .prettierrc :
bash
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 80
}
使用 :
bash
# 格式化所有文件
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依赖包。
初始化项目 :
bash
npm init -y
这会创建一个 package.json 文件:
bash
{
"name": "my-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
安装依赖包 :
bash
# 安装生产环境依赖
npm install lodash
npm install axios
# 一次性安装多个包
npm install lodash axios
# 安装开发环境依赖
npm install --save-dev prettier eslint
# 简写
npm i lodash
npm i -D prettier
package.json变化 :
bash
{
"dependencies": {
"axios": "^1.6.0",
"lodash": "^4.17.21"
},
"devDependencies": {
"eslint": "^8.50.0",
"prettier": "^3.0.0"
}
}
使用依赖包 :
javascript
// 引入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';
常用命令 :
bash
# 查看已安装的包
npm list
# 查看全局安装的包
npm list -g
# 更新依赖包
npm update
# 卸载包
npm uninstall lodash
# 查看包信息
npm info lodash
# 搜索包
npm search date-fns
3.1.2 yarn和pnpm
yarn和pnpm是npm的替代方案,速度更快。
安装yarn :
bash
npm install -g yarn
使用yarn :
bash
# 初始化项目
yarn init -y
# 安装依赖
yarn add lodash
yarn add --dev prettier
# 删除依赖
yarn remove lodash
# 更新依赖
yarn upgrade
# 安装所有依赖
yarn install
安装pnpm :
bash
npm install -g pnpm
使用pnpm :
bash
# 初始化项目
pnpm init
# 安装依赖
pnpm add lodash
pnpm add -D prettier
# 删除依赖
pnpm remove lodash
# 安装所有依赖
pnpm install
npm、yarn、pnpm对比 :
|--------|---------|----------|----------|
| 特性 | npm | yarn | pnpm |
| 安装速度 | 慢 | 快 | 最快 |
| 磁盘占用 | 大 | 中 | 小 |
| 兼容性 | 最好 | 好 | 较好 |
| 生态 | 最完善 | 完善 | 快速发展 |
3.2 版本控制:Git
Git是分布式版本控制系统,是团队协作的必备工具。
3.2.1 基本概念
• 仓库(Repository) :存储项目文件和版本历史的地方
• 提交(Commit) :保存项目的一个快照
• 分支(Branch) :独立的开发线
• 合并(Merge) :将分支合并到主分支
3.2.2 常用命令
初始化仓库 :
bash
git init
添加文件到暂存区 :
bash
# 添加所有文件
git add .
# 添加指定文件
git add index.html
# 查看暂存区状态
git status
提交到本地仓库 :
bash
git commit -m "feat: 添加待办事项功能"
提交信息规范 :
|----------|--------|------------------|
| 类型 | 说明 | 示例 |
| feat | 新功能 | feat: 添加用户登录功能 |
| fix | 修复bug | fix: 修复登录页面的样式问题 |
| docs | 文档更新 | docs: 更新README文档 |
| style | 代码格式调整 | style: 调整代码缩进 |
| refactor | 重构代码 | refactor: 重构用户模块 |
| test | 测试相关 | test: 添加单元测试 |
| chore | 构建/工具 | chore: 更新依赖包 |
查看提交历史 :
bash
# 查看提交历史
git log
# 查看简洁的提交历史
git log --oneline
# 查看某个文件的修改历史
git log --follow index.html
创建和切换分支 :
bash
# 创建分支
git branch feature/login
# 切换分支
git checkout feature/login
# 创建并切换分支(简写)
git checkout -b feature/login
# 查看所有分支
git branch
# 删除分支
git branch -d feature/login
合并分支 :
bash
# 切换到主分支
git checkout main
# 合并feature分支
git merge feature/login
# 删除已合并的分支
git branch -d feature/login
推送到远程仓库 :
bash
# 关联远程仓库
git remote add origin https://github.com/username/repo.git
# 推送到远程仓库
git push origin main
# 推送所有分支
git push --all origin
# 推送标签
git push --tags
从远程仓库拉取 :
bash
# 拉取远程更新
git pull origin main
# 获取远程更新但不合并
git fetch origin main
# 查看远程仓库
git remote -v
3.2.3 .gitignore文件
.gitignore文件指定哪些文件不需要被Git跟踪。
示例 .gitignore :
bash
# 依赖
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是当前最流行的模块打包工具。
安装 :
bash
npm install --save-dev webpack webpack-cli
基本配置 webpack.config.js :
javascript
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
}
};
安装必要的依赖 :
bash
npm install --save-dev html-webpack-plugin style-loader css-loader babel-loader @babel/core @babel/preset-env webpack-dev-server
package.json添加脚本:
bash
{
"scripts": {
"dev": "webpack serve --mode development",
"build": "webpack --mode production"
}
}
使用 :
bash
# 开发模式(启动开发服务器)
npm run dev
# 生产模式(打包)
npm run build
3.3.2 Vite
Vite是新一代构建工具,速度更快。
安装 :
bash
npm create vite@latest my-project
cd my-project
npm install
开发 :
bash
npm run dev
构建 :
bash
npm run build
Vite vs Webpack对比 :
|--------|-------------|----------|
| 特性 | Webpack | Vite |
| 启动速度 | 慢(需要打包) | 快(按需编译) |
| 热更新 | 较慢 | 极快 |
| 配置复杂度 | 高 | 低 |
| 生态 | 完善 | 快速发展 |
| 适用场景 | 大型项目 | 现代项目 |
3.4 调试技巧
3.4.1 Chrome开发者工具
Elements面板 :查看和修改HTML、CSS
Console面板 :查看日志、执行JavaScript
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 断点调试
在代码中设置断点 :
javascript
function calculateSum(a, b) {
debugger; // 在这里设置断点,代码执行会暂停
const sum = a + b;
return sum;
}
calculateSum(10, 20);
在开发者工具中设置断点 :
-
打开Sources面板
-
找到要调试的文件
-
点击行号设置断点
-
刷新页面或触发代码执行
-
使用调试控制按钮(继续、单步进入、单步跳出、单步跳过)
本篇知识总结
性能优化篇
|---------|-----------------------------------|
| 知识点 | 内容 |
| 性能指标 | FP、FCP、LCP、TTI、CLS、FID |
| 资源优化 | 合并文件、压缩、CDN、图片优化、懒加载 |
| 代码优化 | CSS选择器优化、DOM操作优化、事件委托、防抖节流 |
| 缓存策略 | 浏览器缓存、LocalStorage、Service Worker |
代码质量篇
|---------|---------------------------------|
| 知识点 | 内容 |
| 模块化 | ES6模块、职责分离、文件组织 |
| 设计模式 | 单例模式、观察者模式、工厂模式、策略模式 |
| 代码规范 | 命名规范、注释规范、代码格式(Prettier、ESLint) |
工程化篇
|---------|-------------------------|
| 知识点 | 内容 |
| 包管理器 | npm、yarn、pnpm |
| 版本控制 | Git基本操作、分支管理、.gitignore |
| 构建工具 | Webpack、Vite |
| 调试技巧 | Chrome开发者工具、断点调试 |
课后练习
练习1:性能优化
找一个简单的网页,完成以下优化:
-
合并CSS和JS文件
-
压缩所有资源文件
-
为图片添加懒加载
-
使用CDN加载jQuery或Vue等库
-
使用Lighthouse测试优化效果
练习2:模块化重构
将之前写的待办事项应用重构为模块化结构:
-
拆分成Task、TaskService、TaskView三个模块
-
使用ES6模块化语法
-
使用Webpack或Vite构建项目
练习3:Git版本控制
为你的项目添加Git版本控制:
-
初始化Git仓库
-
创建.gitignore文件
-
提交代码到本地仓库
-
在GitHub上创建远程仓库
-
推送代码到远程仓库
练习4:使用构建工具
使用Vite创建一个新项目:
-
安装Vite
-
创建项目
-
配置项目
-
开发和构建
练习5:性能分析
使用Chrome开发者工具分析一个网站:
-
打开Lighthouse,生成性能报告
-
分析Network面板,找出加载慢的资源
-
分析Performance面板,找出性能瓶颈
-
提出优化建议
结语
进阶(一)我们学习了前端性能优化、代码质量提升和工程化实践。这些都是专业前端工程师必备的技能。
如果说基础系列教你"如何写代码",那么进阶系列教你"如何成为专业开发者"。
下一篇进阶文章,我们将深入学习:
• 前端框架(Vue/React)
• TypeScript基础
• 前端测试
• 部署与CI/CD
版权声明 :
本文为原创文章,转载请注明出处。未经作者许可,禁止用于商业用途。