From 91a79e39a941017e739bc9c9b1d8e1631a5bcfee Mon Sep 17 00:00:00 2001
From: Tu Shaokun <2801884530@qq.com>
Date: Sat, 29 Nov 2025 21:58:45 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=80=A7=E8=83=BD?=
=?UTF-8?q?=E5=AE=A1=E6=9F=A5=E6=8C=87=E5=8D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Core Web Vitals (LCP, INP, CLS, FCP, TBT)
- JavaScript 性能(代码分割、Bundle 优化、虚拟列表)
- 内存管理(泄漏检测、清理模式)
- 数据库性能(N+1、索引、查询优化)
- API 性能(分页、缓存、限流)
- 算法复杂度(Big O、常见优化)
- 性能度量阈值和工具推荐
---
SKILL.md | 1 +
reference/performance-review-guide.md | 752 ++++++++++++++++++++++++++
2 files changed, 753 insertions(+)
create mode 100644 reference/performance-review-guide.md
diff --git a/SKILL.md b/SKILL.md
index 7477cfd..d251831 100644
--- a/SKILL.md
+++ b/SKILL.md
@@ -180,6 +180,7 @@ Use labels to indicate priority:
## Additional Resources
- [Architecture Review Guide](reference/architecture-review-guide.md) - 架构设计审查指南(SOLID、反模式、耦合度)
+- [Performance Review Guide](reference/performance-review-guide.md) - 性能审查指南(Web Vitals、N+1、复杂度)
- [Common Bugs Checklist](reference/common-bugs-checklist.md) - 按语言分类的常见错误清单
- [Security Review Guide](reference/security-review-guide.md) - 安全审查指南
- [Code Review Best Practices](reference/code-review-best-practices.md) - 代码审查最佳实践
diff --git a/reference/performance-review-guide.md b/reference/performance-review-guide.md
new file mode 100644
index 0000000..87a8ba7
--- /dev/null
+++ b/reference/performance-review-guide.md
@@ -0,0 +1,752 @@
+# Performance Review Guide
+
+性能审查指南,覆盖前端、后端、数据库、算法复杂度和 API 性能。
+
+## 目录
+
+- [前端性能 (Core Web Vitals)](#前端性能-core-web-vitals)
+- [JavaScript 性能](#javascript-性能)
+- [内存管理](#内存管理)
+- [数据库性能](#数据库性能)
+- [API 性能](#api-性能)
+- [算法复杂度](#算法复杂度)
+- [性能审查清单](#性能审查清单)
+
+---
+
+## 前端性能 (Core Web Vitals)
+
+### 2024 核心指标
+
+| 指标 | 全称 | 目标值 | 含义 |
+|------|------|--------|------|
+| **LCP** | Largest Contentful Paint | ≤ 2.5s | 最大内容绘制时间 |
+| **INP** | Interaction to Next Paint | ≤ 200ms | 交互响应时间(2024 年替代 FID)|
+| **CLS** | Cumulative Layout Shift | ≤ 0.1 | 累积布局偏移 |
+| **FCP** | First Contentful Paint | ≤ 1.8s | 首次内容绘制 |
+| **TBT** | Total Blocking Time | ≤ 200ms | 主线程阻塞时间 |
+
+### LCP 优化检查
+
+```javascript
+// ❌ LCP 图片懒加载 - 延迟关键内容
+
+
+// ✅ LCP 图片立即加载
+
+
+// ❌ 未优化的图片格式
+
// PNG 文件过大
+
+// ✅ 现代图片格式 + 响应式
+
+
+
+
+
+```
+
+**审查要点:**
+- [ ] LCP 元素是否设置 `fetchpriority="high"`?
+- [ ] 是否使用 WebP/AVIF 格式?
+- [ ] 是否有服务端渲染或静态生成?
+- [ ] CDN 是否配置正确?
+
+### FCP 优化检查
+
+```html
+
+
+
+
+
+
+
+
+@font-face {
+ font-family: 'CustomFont';
+ src: url('font.woff2');
+}
+
+
+@font-face {
+ font-family: 'CustomFont';
+ src: url('font.woff2');
+ font-display: swap; /* 先用系统字体,加载后切换 */
+}
+```
+
+### INP 优化检查
+
+```javascript
+// ❌ 长任务阻塞主线程
+button.addEventListener('click', () => {
+ // 耗时 500ms 的同步操作
+ processLargeData(data);
+ updateUI();
+});
+
+// ✅ 拆分长任务
+button.addEventListener('click', async () => {
+ // 让出主线程
+ await scheduler.yield?.() ?? new Promise(r => setTimeout(r, 0));
+
+ // 分批处理
+ for (const chunk of chunks) {
+ processChunk(chunk);
+ await scheduler.yield?.();
+ }
+ updateUI();
+});
+
+// ✅ 使用 Web Worker 处理复杂计算
+const worker = new Worker('heavy-computation.js');
+worker.postMessage(data);
+worker.onmessage = (e) => updateUI(e.data);
+```
+
+### CLS 优化检查
+
+```css
+/* ❌ 未指定尺寸的媒体 */
+img { width: 100%; }
+
+/* ✅ 预留空间 */
+img {
+ width: 100%;
+ aspect-ratio: 16 / 9;
+}
+
+/* ❌ 动态插入内容导致布局偏移 */
+.ad-container { }
+
+/* ✅ 预留固定高度 */
+.ad-container {
+ min-height: 250px;
+}
+```
+
+**CLS 审查清单:**
+- [ ] 图片/视频是否有 width/height 或 aspect-ratio?
+- [ ] 字体加载是否使用 `font-display: swap`?
+- [ ] 动态内容是否预留空间?
+- [ ] 是否避免在现有内容上方插入内容?
+
+---
+
+## JavaScript 性能
+
+### 代码分割与懒加载
+
+```javascript
+// ❌ 一次性加载所有代码
+import { HeavyChart } from './charts';
+import { PDFExporter } from './pdf';
+import { AdminPanel } from './admin';
+
+// ✅ 按需加载
+const HeavyChart = lazy(() => import('./charts'));
+const PDFExporter = lazy(() => import('./pdf'));
+
+// ✅ 路由级代码分割
+const routes = [
+ {
+ path: '/dashboard',
+ component: lazy(() => import('./pages/Dashboard')),
+ },
+ {
+ path: '/admin',
+ component: lazy(() => import('./pages/Admin')),
+ },
+];
+```
+
+### Bundle 体积优化
+
+```javascript
+// ❌ 导入整个库
+import _ from 'lodash';
+import moment from 'moment';
+
+// ✅ 按需导入
+import debounce from 'lodash/debounce';
+import { format } from 'date-fns';
+
+// ❌ 未使用 Tree Shaking
+export default {
+ fn1() {},
+ fn2() {}, // 未使用但被打包
+};
+
+// ✅ 命名导出支持 Tree Shaking
+export function fn1() {}
+export function fn2() {}
+```
+
+**Bundle 审查清单:**
+- [ ] 是否使用动态 import() 进行代码分割?
+- [ ] 大型库是否按需导入?
+- [ ] 是否分析过 bundle 大小?(webpack-bundle-analyzer)
+- [ ] 是否有未使用的依赖?
+
+### 列表渲染优化
+
+```javascript
+// ❌ 渲染大列表
+function List({ items }) {
+ return (
+
+ {items.map(item => - {item.name}
)}
+
+ ); // 10000 条数据 = 10000 个 DOM 节点
+}
+
+// ✅ 虚拟列表 - 只渲染可见项
+import { FixedSizeList } from 'react-window';
+
+function VirtualList({ items }) {
+ return (
+
+ {({ index, style }) => (
+ {items[index].name}
+ )}
+
+ );
+}
+```
+
+**大数据审查要点:**
+- [ ] 列表超过 100 项是否使用虚拟滚动?
+- [ ] 表格是否支持分页或虚拟化?
+- [ ] 是否有不必要的全量渲染?
+
+---
+
+## 内存管理
+
+### 常见内存泄漏
+
+#### 1. 未清理的事件监听
+
+```javascript
+// ❌ 组件卸载后事件仍在监听
+useEffect(() => {
+ window.addEventListener('resize', handleResize);
+}, []);
+
+// ✅ 清理事件监听
+useEffect(() => {
+ window.addEventListener('resize', handleResize);
+ return () => window.removeEventListener('resize', handleResize);
+}, []);
+```
+
+#### 2. 未清理的定时器
+
+```javascript
+// ❌ 定时器未清理
+useEffect(() => {
+ setInterval(fetchData, 5000);
+}, []);
+
+// ✅ 清理定时器
+useEffect(() => {
+ const timer = setInterval(fetchData, 5000);
+ return () => clearInterval(timer);
+}, []);
+```
+
+#### 3. 闭包引用
+
+```javascript
+// ❌ 闭包持有大对象引用
+function createHandler() {
+ const largeData = new Array(1000000).fill('x');
+
+ return function handler() {
+ // largeData 被闭包引用,无法被回收
+ console.log(largeData.length);
+ };
+}
+
+// ✅ 只保留必要数据
+function createHandler() {
+ const largeData = new Array(1000000).fill('x');
+ const length = largeData.length; // 只保留需要的值
+
+ return function handler() {
+ console.log(length);
+ };
+}
+```
+
+#### 4. 未清理的订阅
+
+```javascript
+// ❌ WebSocket/EventSource 未关闭
+useEffect(() => {
+ const ws = new WebSocket('wss://...');
+ ws.onmessage = handleMessage;
+}, []);
+
+// ✅ 清理连接
+useEffect(() => {
+ const ws = new WebSocket('wss://...');
+ ws.onmessage = handleMessage;
+ return () => ws.close();
+}, []);
+```
+
+### 内存审查清单
+
+```markdown
+- [ ] useEffect 是否都有清理函数?
+- [ ] 事件监听是否在组件卸载时移除?
+- [ ] 定时器是否被清理?
+- [ ] WebSocket/SSE 连接是否关闭?
+- [ ] 大对象是否及时释放?
+- [ ] 是否有全局变量累积数据?
+```
+
+### 检测工具
+
+| 工具 | 用途 |
+|------|------|
+| Chrome DevTools Memory | 堆快照分析 |
+| MemLab (Meta) | 自动化内存泄漏检测 |
+| Performance Monitor | 实时内存监控 |
+
+---
+
+## 数据库性能
+
+### N+1 查询问题
+
+```python
+# ❌ N+1 问题 - 1 + N 次查询
+users = User.objects.all() # 1 次查询
+for user in users:
+ print(user.profile.bio) # N 次查询(每个用户一次)
+
+# ✅ Eager Loading - 2 次查询
+users = User.objects.select_related('profile').all()
+for user in users:
+ print(user.profile.bio) # 无额外查询
+
+# ✅ 多对多关系用 prefetch_related
+posts = Post.objects.prefetch_related('tags').all()
+```
+
+```javascript
+// TypeORM 示例
+// ❌ N+1 问题
+const users = await userRepository.find();
+for (const user of users) {
+ const posts = await user.posts; // 每次循环都查询
+}
+
+// ✅ Eager Loading
+const users = await userRepository.find({
+ relations: ['posts'],
+});
+```
+
+### 索引优化
+
+```sql
+-- ❌ 全表扫描
+SELECT * FROM orders WHERE status = 'pending';
+
+-- ✅ 添加索引
+CREATE INDEX idx_orders_status ON orders(status);
+
+-- ❌ 索引失效:函数操作
+SELECT * FROM users WHERE YEAR(created_at) = 2024;
+
+-- ✅ 范围查询可用索引
+SELECT * FROM users
+WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
+
+-- ❌ 索引失效:LIKE 前缀通配符
+SELECT * FROM products WHERE name LIKE '%phone%';
+
+-- ✅ 前缀匹配可用索引
+SELECT * FROM products WHERE name LIKE 'phone%';
+```
+
+### 查询优化
+
+```sql
+-- ❌ SELECT * 获取不需要的列
+SELECT * FROM users WHERE id = 1;
+
+-- ✅ 只查询需要的列
+SELECT id, name, email FROM users WHERE id = 1;
+
+-- ❌ 大表无 LIMIT
+SELECT * FROM logs WHERE type = 'error';
+
+-- ✅ 分页查询
+SELECT * FROM logs WHERE type = 'error' LIMIT 100 OFFSET 0;
+
+-- ❌ 在循环中执行查询
+for id in user_ids:
+ cursor.execute("SELECT * FROM users WHERE id = %s", (id,))
+
+-- ✅ 批量查询
+cursor.execute("SELECT * FROM users WHERE id IN %s", (tuple(user_ids),))
+```
+
+### 数据库审查清单
+
+```markdown
+🔴 必须检查:
+- [ ] 是否存在 N+1 查询?
+- [ ] WHERE 子句列是否有索引?
+- [ ] 是否避免了 SELECT *?
+- [ ] 大表查询是否有 LIMIT?
+
+🟡 建议检查:
+- [ ] 是否使用了 EXPLAIN 分析查询计划?
+- [ ] 复合索引列顺序是否正确?
+- [ ] 是否有未使用的索引?
+- [ ] 是否有慢查询日志监控?
+```
+
+---
+
+## API 性能
+
+### 分页实现
+
+```javascript
+// ❌ 返回全部数据
+app.get('/users', async (req, res) => {
+ const users = await User.findAll(); // 可能返回 100000 条
+ res.json(users);
+});
+
+// ✅ 分页 + 限制最大数量
+app.get('/users', async (req, res) => {
+ const page = parseInt(req.query.page) || 1;
+ const limit = Math.min(parseInt(req.query.limit) || 20, 100); // 最大 100
+ const offset = (page - 1) * limit;
+
+ const { rows, count } = await User.findAndCountAll({
+ limit,
+ offset,
+ order: [['id', 'ASC']],
+ });
+
+ res.json({
+ data: rows,
+ pagination: {
+ page,
+ limit,
+ total: count,
+ totalPages: Math.ceil(count / limit),
+ },
+ });
+});
+```
+
+### 缓存策略
+
+```javascript
+// ✅ Redis 缓存示例
+async function getUser(id) {
+ const cacheKey = `user:${id}`;
+
+ // 1. 检查缓存
+ const cached = await redis.get(cacheKey);
+ if (cached) {
+ return JSON.parse(cached);
+ }
+
+ // 2. 查询数据库
+ const user = await db.users.findById(id);
+
+ // 3. 写入缓存(设置过期时间)
+ await redis.setex(cacheKey, 3600, JSON.stringify(user));
+
+ return user;
+}
+
+// ✅ HTTP 缓存头
+app.get('/static-data', (req, res) => {
+ res.set({
+ 'Cache-Control': 'public, max-age=86400', // 24 小时
+ 'ETag': 'abc123',
+ });
+ res.json(data);
+});
+```
+
+### 响应压缩
+
+```javascript
+// ✅ 启用 Gzip/Brotli 压缩
+const compression = require('compression');
+app.use(compression());
+
+// ✅ 只返回必要字段
+// 请求: GET /users?fields=id,name,email
+app.get('/users', async (req, res) => {
+ const fields = req.query.fields?.split(',') || ['id', 'name'];
+ const users = await User.findAll({
+ attributes: fields,
+ });
+ res.json(users);
+});
+```
+
+### 限流保护
+
+```javascript
+// ✅ 速率限制
+const rateLimit = require('express-rate-limit');
+
+const limiter = rateLimit({
+ windowMs: 60 * 1000, // 1 分钟
+ max: 100, // 最多 100 次请求
+ message: { error: 'Too many requests, please try again later.' },
+});
+
+app.use('/api/', limiter);
+```
+
+### API 审查清单
+
+```markdown
+- [ ] 列表接口是否有分页?
+- [ ] 是否限制了每页最大数量?
+- [ ] 热点数据是否有缓存?
+- [ ] 是否启用了响应压缩?
+- [ ] 是否有速率限制?
+- [ ] 是否只返回必要字段?
+```
+
+---
+
+## 算法复杂度
+
+### 常见复杂度对比
+
+| 复杂度 | 名称 | 10 条 | 1000 条 | 100 万条 | 示例 |
+|--------|------|-------|---------|----------|------|
+| O(1) | 常数 | 1 | 1 | 1 | 哈希查找 |
+| O(log n) | 对数 | 3 | 10 | 20 | 二分查找 |
+| O(n) | 线性 | 10 | 1000 | 100 万 | 遍历数组 |
+| O(n log n) | 线性对数 | 33 | 10000 | 2000 万 | 快速排序 |
+| O(n²) | 平方 | 100 | 100 万 | 1 万亿 | 嵌套循环 |
+| O(2ⁿ) | 指数 | 1024 | ∞ | ∞ | 递归斐波那契 |
+
+### 代码审查中的识别
+
+```javascript
+// ❌ O(n²) - 嵌套循环
+function findDuplicates(arr) {
+ const duplicates = [];
+ for (let i = 0; i < arr.length; i++) {
+ for (let j = i + 1; j < arr.length; j++) {
+ if (arr[i] === arr[j]) {
+ duplicates.push(arr[i]);
+ }
+ }
+ }
+ return duplicates;
+}
+
+// ✅ O(n) - 使用 Set
+function findDuplicates(arr) {
+ const seen = new Set();
+ const duplicates = new Set();
+ for (const item of arr) {
+ if (seen.has(item)) {
+ duplicates.add(item);
+ }
+ seen.add(item);
+ }
+ return [...duplicates];
+}
+```
+
+```javascript
+// ❌ O(n²) - 每次循环都调用 includes
+function removeDuplicates(arr) {
+ const result = [];
+ for (const item of arr) {
+ if (!result.includes(item)) { // includes 是 O(n)
+ result.push(item);
+ }
+ }
+ return result;
+}
+
+// ✅ O(n) - 使用 Set
+function removeDuplicates(arr) {
+ return [...new Set(arr)];
+}
+```
+
+```javascript
+// ❌ O(n) 查找 - 每次都遍历
+const users = [{ id: 1, name: 'A' }, { id: 2, name: 'B' }, ...];
+
+function getUser(id) {
+ return users.find(u => u.id === id); // O(n)
+}
+
+// ✅ O(1) 查找 - 使用 Map
+const userMap = new Map(users.map(u => [u.id, u]));
+
+function getUser(id) {
+ return userMap.get(id); // O(1)
+}
+```
+
+### 空间复杂度考虑
+
+```javascript
+// ⚠️ O(n) 空间 - 创建新数组
+const doubled = arr.map(x => x * 2);
+
+// ✅ O(1) 空间 - 原地修改(如果允许)
+for (let i = 0; i < arr.length; i++) {
+ arr[i] *= 2;
+}
+
+// ⚠️ 递归深度过大可能栈溢出
+function factorial(n) {
+ if (n <= 1) return 1;
+ return n * factorial(n - 1); // O(n) 栈空间
+}
+
+// ✅ 迭代版本 O(1) 空间
+function factorial(n) {
+ let result = 1;
+ for (let i = 2; i <= n; i++) {
+ result *= i;
+ }
+ return result;
+}
+```
+
+### 复杂度审查问题
+
+```markdown
+💡 "这个嵌套循环的复杂度是 O(n²),数据量大时会有性能问题"
+🔴 "这里用 Array.includes() 在循环中,整体是 O(n²),建议用 Set"
+🟡 "这个递归深度可能导致栈溢出,建议改为迭代或尾递归"
+```
+
+---
+
+## 性能审查清单
+
+### 🔴 必须检查(阻塞级)
+
+**前端:**
+- [ ] LCP 图片是否懒加载?(不应该)
+- [ ] 是否有 `transition: all`?
+- [ ] 是否动画 width/height/top/left?
+- [ ] 列表 >100 项是否虚拟化?
+
+**后端:**
+- [ ] 是否存在 N+1 查询?
+- [ ] 列表接口是否有分页?
+- [ ] 是否有 SELECT * 查大表?
+
+**通用:**
+- [ ] 是否有 O(n²) 或更差的嵌套循环?
+- [ ] useEffect/事件监听是否有清理?
+
+### 🟡 建议检查(重要级)
+
+**前端:**
+- [ ] 是否使用代码分割?
+- [ ] 大型库是否按需导入?
+- [ ] 图片是否使用 WebP/AVIF?
+- [ ] 是否有未使用的依赖?
+
+**后端:**
+- [ ] 热点数据是否有缓存?
+- [ ] WHERE 列是否有索引?
+- [ ] 是否有慢查询监控?
+
+**API:**
+- [ ] 是否启用响应压缩?
+- [ ] 是否有速率限制?
+- [ ] 是否只返回必要字段?
+
+### 🟢 优化建议(建议级)
+
+- [ ] 是否分析过 bundle 大小?
+- [ ] 是否使用 CDN?
+- [ ] 是否有性能监控?
+- [ ] 是否做过性能基准测试?
+
+---
+
+## 性能度量阈值
+
+### 前端指标
+
+| 指标 | 好 | 需改进 | 差 |
+|------|-----|--------|-----|
+| LCP | ≤ 2.5s | 2.5-4s | > 4s |
+| INP | ≤ 200ms | 200-500ms | > 500ms |
+| CLS | ≤ 0.1 | 0.1-0.25 | > 0.25 |
+| FCP | ≤ 1.8s | 1.8-3s | > 3s |
+| Bundle Size (JS) | < 200KB | 200-500KB | > 500KB |
+
+### 后端指标
+
+| 指标 | 好 | 需改进 | 差 |
+|------|-----|--------|-----|
+| API 响应时间 | < 100ms | 100-500ms | > 500ms |
+| 数据库查询 | < 50ms | 50-200ms | > 200ms |
+| 页面加载 | < 3s | 3-5s | > 5s |
+
+---
+
+## 工具推荐
+
+### 前端性能
+
+| 工具 | 用途 |
+|------|------|
+| [Lighthouse](https://developer.chrome.com/docs/lighthouse/) | Core Web Vitals 测试 |
+| [WebPageTest](https://www.webpagetest.org/) | 详细性能分析 |
+| [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) | Bundle 分析 |
+| [Chrome DevTools Performance](https://developer.chrome.com/docs/devtools/performance/) | 运行时性能分析 |
+
+### 内存检测
+
+| 工具 | 用途 |
+|------|------|
+| [MemLab](https://github.com/facebookincubator/memlab) | 自动化内存泄漏检测 |
+| Chrome Memory Tab | 堆快照分析 |
+
+### 后端性能
+
+| 工具 | 用途 |
+|------|------|
+| EXPLAIN | 数据库查询计划分析 |
+| [pganalyze](https://pganalyze.com/) | PostgreSQL 性能监控 |
+| [New Relic](https://newrelic.com/) / [Datadog](https://www.datadoghq.com/) | APM 监控 |
+
+---
+
+## 参考资源
+
+- [Core Web Vitals - web.dev](https://web.dev/articles/vitals)
+- [Optimizing Core Web Vitals - Vercel](https://vercel.com/guides/optimizing-core-web-vitals-in-2024)
+- [MemLab - Meta Engineering](https://engineering.fb.com/2022/09/12/open-source/memlab/)
+- [Big O Cheat Sheet](https://www.bigocheatsheet.com/)
+- [N+1 Query Problem - Stack Overflow](https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping)
+- [API Performance Optimization](https://algorithmsin60days.com/blog/optimizing-api-performance/)