# 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 文件过大 // ✅ 现代图片格式 + 响应式 Hero ``` **审查要点:** - [ ] 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 ( ); // 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/)