前言
基础系列教你"怎么写代码",进阶系列教你"如何写好代码"。
如果用烹饪来比喻:
• 基础系列:教你认识食材,掌握基本的刀工和火候
• 进阶系列:教你食材搭配、营养搭配、摆盘艺术、厨房管理
这篇进阶文章,我们将聚焦三个核心方向:
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" 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选择器越复杂,匹配越慢。
优化前:
/* 深度嵌套选择器,性能差 */
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" 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:
// 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 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>
模块化的优势:
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.com
2.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 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)
定义一系列算法,把它们封装起来,并使它们可以互相替换。
应用场景:表单验证、排序算法、支付方式
// 策略模式示例:表单验证
// 策略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 prettier
package.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-fns
3.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 install
npm、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 -v
3.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 build
3.3.2 Vite
Vite是新一代构建工具,速度更快。
安装:
npm create vite@latest my-project
cd my-project
npm install
开发:
npm run dev
构建:
npm run build
Vite 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
版权声明:
本文为原创文章,转载请注明出处。未经作者许可,禁止用于商业用途。
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/DaiZongFuUp/article/details/158286322



