性能预算制定与管理完全指南
什么是性能预算
性能预算(Performance Budget)是为网站性能指标设定的量化限制。就像财务预算控制支出一样,性能预算控制着网站的"性能支出"。
性能预算的核心概念:
┌─────────────────────────────────────────────────────────────┐
│ 性能预算 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 指标类型 预算值 当前值 状态 │
│ ───────────────────────────────────────────────────────── │
│ 📦 JS Bundle < 200KB 185KB ✅ │
│ 🎨 CSS Bundle < 50KB 48KB ✅ │
│ 🖼️ 图片总大小 < 1MB 1.2MB ❌ │
│ ⏱️ LCP < 2.5s 2.1s ✅ │
│ ⚡ FID < 100ms 85ms ✅ │
│ 📐 CLS < 0.1 0.15 ❌ │
│ 🚀 TTI < 3.8s 4.2s ❌ │
│ │
│ 总体状态: 4/7 通过 (57%) 需要优化! │
└─────────────────────────────────────────────────────────────┘
为什么需要性能预算
1. 预防性能退化
没有预算限制,性能会像无预算的项目一样失控:
典型的性能退化曲线:
性能
得分
100 │
90 │ ●
80 │ ●
70 │ ● 新功能A
60 │ ● 新功能B
50 │ ● 第三方SDK
40 │ ● 埋点代码
30 │ ● A/B测试
20 │ ●
└─────────────────────────→ 时间
有性能预算后:
性能
得分
100 │
90 │ ●──●──●──●──●──●──●──● ← 预算线保护
80 │
└─────────────────────────→ 时间
2. 建立性能文化
性能预算让性能成为可量化、可追踪的目标,而非模糊的"感觉快"。
3. 在功能和性能间取得平衡
当资源有限时,预算帮助团队做出取舍决策。
性能预算的类型
1. 基于时间的预算
// 时间预算配置
const timeBasedBudget = {
// 加载时间指标
metrics: {
// 首次内容绘制
FCP: {
budget: 1800, // ms
good: 1000,
needsImprovement: 3000
},
// 最大内容绘制
LCP: {
budget: 2500, // ms
good: 2500,
needsImprovement: 4000
},
// 可交互时间
TTI: {
budget: 3800, // ms
good: 3800,
needsImprovement: 7300
},
// 总阻塞时间
TBT: {
budget: 200, // ms
good: 200,
needsImprovement: 600
},
// 首次输入延迟
FID: {
budget: 100, // ms
good: 100,
needsImprovement: 300
}
},
// 评估方法
evaluate(measured) {
const results = {};
for (const [metric, config] of Object.entries(this.metrics)) {
const value = measured[metric];
results[metric] = {
value,
budget: config.budget,
passed: value <= config.budget,
status: value <= config.good ? 'good' :
value <= config.needsImprovement ? 'needs-improvement' : 'poor'
};
}
return results;
}
};
2. 基于资源大小的预算
// 资源大小预算配置
const sizeBasedBudget = {
// 按资源类型
resourceTypes: {
script: {
budget: 300 * 1024, // 300KB
description: 'JavaScript 总大小'
},
stylesheet: {
budget: 100 * 1024, // 100KB
description: 'CSS 总大小'
},
image: {
budget: 500 * 1024, // 500KB
description: '图片总大小'
},
font: {
budget: 100 * 1024, // 100KB
description: '字体总大小'
},
total: {
budget: 1024 * 1024, // 1MB
description: '页面总大小'
}
},
// 按入口文件
entryPoints: {
'main.js': {
budget: 150 * 1024, // 150KB
description: '主 Bundle'
},
'vendor.js': {
budget: 100 * 1024, // 100KB
description: '第三方库 Bundle'
}
},
// 按路由
routes: {
'/': {
script: 200 * 1024,
total: 800 * 1024
},
'/dashboard': {
script: 300 * 1024,
total: 1024 * 1024
}
}
};
3. 基于数量的预算
// 数量预算配置
const countBasedBudget = {
// HTTP 请求数
requests: {
script: { budget: 10, description: 'JS 请求数' },
stylesheet: { budget: 5, description: 'CSS 请求数' },
image: { budget: 20, description: '图片请求数' },
font: { budget: 4, description: '字体请求数' },
total: { budget: 50, description: '总请求数' }
},
// DOM 元素数
domElements: {
budget: 1500,
warning: 1000,
description: 'DOM 节点总数'
},
// 第三方脚本
thirdPartyScripts: {
budget: 5,
description: '第三方脚本数量'
},
// 自定义字体
customFonts: {
budget: 3,
description: '自定义字体数量'
}
};
4. 基于规则的预算
// 规则预算配置
const ruleBasedBudget = {
rules: [
{
id: 'no-unminified-js',
description: '所有 JS 必须压缩',
check: (resources) => {
const jsFiles = resources.filter(r => r.type === 'script');
return jsFiles.every(f => !f.name.includes('.min.') || f.isMinified);
}
},
{
id: 'images-optimized',
description: '图片必须使用现代格式',
check: (resources) => {
const images = resources.filter(r => r.type === 'image');
const modernFormats = ['webp', 'avif'];
return images.every(img =>
modernFormats.some(fmt => img.name.endsWith(`.${fmt}`))
);
}
},
{
id: 'no-blocking-resources',
description: '关键路径不能有阻塞资源',
check: (resources) => {
return resources
.filter(r => r.isRenderBlocking)
.length === 0;
}
},
{
id: 'font-display-swap',
description: '字体必须使用 font-display: swap',
check: (stylesheets) => {
return stylesheets.every(css =>
!css.content.includes('@font-face') ||
css.content.includes('font-display: swap')
);
}
}
]
};
如何制定合理的性能预算
步骤1:确定业务目标
// 性能与业务目标对应关系
const businessGoals = {
// 电商网站
ecommerce: {
// 转化率每秒下降 7%
conversionRateImpact: 0.07,
// 关键指标
criticalMetrics: ['LCP', 'FID', 'CLS'],
// 预算策略
budgetStrategy: {
LCP: 2000, // 产品图片快速展示
FID: 50, // 添加购物车响应快
CLS: 0.05 // 避免布局抖动影响点击
}
},
// 内容网站
content: {
// 跳出率每秒增加 10%
bounceRateImpact: 0.10,
criticalMetrics: ['FCP', 'LCP', 'TTI'],
budgetStrategy: {
FCP: 1000, // 内容快速可见
LCP: 2000, // 主图片/标题快速加载
TTI: 3000 // 快速可阅读
}
},
// SaaS 应用
saas: {
criticalMetrics: ['TTI', 'FID', 'TBT'],
budgetStrategy: {
TTI: 4000, // 可接受较长初始加载
FID: 50, // 交互必须流畅
TBT: 150 // 操作时不能卡顿
}
}
};
步骤2:分析竞争对手
// 竞品性能分析工具
async function analyzeCompetitors(competitors) {
const results = {};
for (const url of competitors) {
try {
// 使用 PageSpeed Insights API
const response = await fetch(
`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(url)}&strategy=mobile`
);
const data = await response.json();
const metrics = data.lighthouseResult.audits;
results[url] = {
FCP: metrics['first-contentful-paint'].numericValue,
LCP: metrics['largest-contentful-paint'].numericValue,
TTI: metrics['interactive'].numericValue,
TBT: metrics['total-blocking-time'].numericValue,
CLS: metrics['cumulative-layout-shift'].numericValue,
score: data.lighthouseResult.categories.performance.score * 100
};
} catch (error) {
results[url] = { error: error.message };
}
}
// 计算中位数和最佳值
const allMetrics = Object.values(results).filter(r => !r.error);
return {
competitors: results,
benchmarks: {
FCP: {
median: median(allMetrics.map(m => m.FCP)),
best: Math.min(...allMetrics.map(m => m.FCP)),
target: Math.min(...allMetrics.map(m => m.FCP)) * 0.8 // 比最好的还快 20%
},
LCP: {
median: median(allMetrics.map(m => m.LCP)),
best: Math.min(...allMetrics.map(m => m.LCP)),
target: Math.min(...allMetrics.map(m => m.LCP)) * 0.8
}
// ... 其他指标
}
};
}
function median(arr) {
const sorted = [...arr].sort((a, b) => a - b);
const mid = Math.floor(sorted.length / 2);
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
}
步骤3:确定预算阈值
// 性能预算计算器
class PerformanceBudgetCalculator {
constructor(config) {
this.businessType = config.businessType;
this.targetUsers = config.targetUsers;
this.currentMetrics = config.currentMetrics;
this.competitorBenchmarks = config.competitorBenchmarks;
}
calculate() {
const budget = {};
// Core Web Vitals 预算
budget.webVitals = this.calculateWebVitalsBudget();
// 资源预算
budget.resources = this.calculateResourceBudget();
// 请求预算
budget.requests = this.calculateRequestBudget();
return budget;
}
calculateWebVitalsBudget() {
// 基于 Google 推荐阈值
const googleThresholds = {
LCP: { good: 2500, poor: 4000 },
FID: { good: 100, poor: 300 },
CLS: { good: 0.1, poor: 0.25 },
FCP: { good: 1800, poor: 3000 },
TTI: { good: 3800, poor: 7300 },
TBT: { good: 200, poor: 600 }
};
const budget = {};
for (const [metric, thresholds] of Object.entries(googleThresholds)) {
const current = this.currentMetrics[metric];
const competitor = this.competitorBenchmarks?.[metric]?.best;
// 取 Google 推荐值和竞品最佳值的较小者
const target = competitor
? Math.min(thresholds.good, competitor * 0.9)
: thresholds.good;
budget[metric] = {
target: Math.round(target),
current,
improvement: current ? Math.round((current - target) / current * 100) : null,
priority: current > thresholds.poor ? 'critical' :
current > thresholds.good ? 'high' : 'normal'
};
}
return budget;
}
calculateResourceBudget() {
// 基于目标设备和网络条件计算
const networkProfiles = {
'4g': {
bandwidth: 10 * 1024 * 1024 / 8, // 10 Mbps = 1.25 MB/s
rtt: 50 // 50ms
},
'3g': {
bandwidth: 1.5 * 1024 * 1024 / 8, // 1.5 Mbps = 187.5 KB/s
rtt: 400 // 400ms
}
};
const targetNetwork = this.targetUsers.network || '4g';
const profile = networkProfiles[targetNetwork];
// 目标:3秒内完成关键资源加载
const loadTimeTarget = 3000; // ms
const availableBytes = profile.bandwidth * (loadTimeTarget / 1000);
return {
total: {
target: Math.round(availableBytes),
description: `${(availableBytes / 1024).toFixed(0)}KB 总大小目标`
},
javascript: {
target: Math.round(availableBytes * 0.35), // JS 占 35%
description: 'JavaScript 预算'
},
css: {
target: Math.round(availableBytes * 0.10), // CSS 占 10%
description: 'CSS 预算'
},
images: {
target: Math.round(availableBytes * 0.40), // 图片占 40%
description: '图片预算'
},
fonts: {
target: Math.round(availableBytes * 0.10), // 字体占 10%
description: '字体预算'
},
other: {
target: Math.round(availableBytes * 0.05), // 其他占 5%
description: '其他资源'
}
};
}
calculateRequestBudget() {
// HTTP/2 多路复用下的合理请求数
return {
critical: {
target: 10,
description: '关键路径请求数'
},
total: {
target: 50,
description: '总请求数'
},
thirdParty: {
target: 5,
description: '第三方脚本数'
}
};
}
}
// 使用示例
const calculator = new PerformanceBudgetCalculator({
businessType: 'ecommerce',
targetUsers: {
network: '4g',
device: 'mobile'
},
currentMetrics: {
LCP: 3200,
FID: 150,
CLS: 0.12
},
competitorBenchmarks: {
LCP: { best: 2100 },
FID: { best: 80 }
}
});
const budget = calculator.calculate();
console.log(budget);
性能预算自动化监控
Webpack 配置
// webpack.config.js
module.exports = {
performance: {
// 资产大小限制
maxAssetSize: 200 * 1024, // 200KB
maxEntrypointSize: 400 * 1024, // 400KB
// 超出预算时的行为:error | warning
hints: process.env.NODE_ENV === 'production' ? 'error' : 'warning',
// 自定义资产过滤
assetFilter: function(assetFilename) {
// 只检查 JS 和 CSS
return /\.(js|css)$/.test(assetFilename);
}
},
plugins: [
// 使用 bundlesize 插件进行更细粒度控制
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: false
})
]
};
bundlesize 配置
// package.json
{
"bundlesize": [
{
"path": "./dist/js/main.*.js",
"maxSize": "150 kB",
"compression": "gzip"
},
{
"path": "./dist/js/vendor.*.js",
"maxSize": "100 kB",
"compression": "gzip"
},
{
"path": "./dist/css/main.*.css",
"maxSize": "30 kB",
"compression": "gzip"
},
{
"path": "./dist/js/*.js",
"maxSize": "300 kB",
"compression": "none"
}
],
"scripts": {
"check-size": "bundlesize"
}
}
Lighthouse CI 配置
// lighthouserc.js
module.exports = {
ci: {
collect: {
// 收集配置
numberOfRuns: 3,
url: [
'http://localhost:3000/',
'http://localhost:3000/products',
'http://localhost:3000/checkout'
],
settings: {
preset: 'desktop',
throttling: {
rttMs: 40,
throughputKbps: 10240,
cpuSlowdownMultiplier: 1
}
}
},
assert: {
// 预算断言
preset: 'lighthouse:recommended',
assertions: {
// Core Web Vitals
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'first-contentful-paint': ['error', { maxNumericValue: 1800 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'total-blocking-time': ['error', { maxNumericValue: 200 }],
// 性能评分
'categories:performance': ['error', { minScore: 0.9 }],
// 资源大小
'resource-summary:script:size': ['error', { maxNumericValue: 300000 }],
'resource-summary:stylesheet:size': ['error', { maxNumericValue: 100000 }],
'resource-summary:image:size': ['error', { maxNumericValue: 500000 }],
// 最佳实践
'uses-responsive-images': 'error',
'uses-webp-images': 'warn',
'render-blocking-resources': 'warn',
'unused-javascript': 'warn'
}
},
upload: {
target: 'temporary-public-storage'
}
}
};
GitHub Actions 集成
# .github/workflows/performance-budget.yml
name: Performance Budget Check
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
bundle-size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Check bundle size
run: npx bundlesize
env:
BUNDLESIZE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Start server
run: npm run preview &
- name: Wait for server
run: npx wait-on http://localhost:3000
- name: Run Lighthouse CI
run: |
npm install -g @lhci/cli
lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
- name: Upload Lighthouse report
uses: actions/upload-artifact@v3
if: always()
with:
name: lighthouse-report
path: .lighthouseci/
web-vitals:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Measure Web Vitals
uses: preactjs/compressed-size-action@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
build-script: "build"
pattern: "./dist/**/*.{js,css,html}"
自定义预算检查脚本
// scripts/check-performance-budget.js
const fs = require('fs');
const path = require('path');
const gzipSize = require('gzip-size');
const chalk = require('chalk');
// 预算配置
const BUDGET = {
resources: {
'dist/js/main.*.js': { maxSize: 150 * 1024, gzip: true },
'dist/js/vendor.*.js': { maxSize: 100 * 1024, gzip: true },
'dist/css/*.css': { maxSize: 50 * 1024, gzip: true }
},
totals: {
javascript: { maxSize: 300 * 1024, gzip: true },
css: { maxSize: 80 * 1024, gzip: true },
images: { maxSize: 500 * 1024, gzip: false },
all: { maxSize: 1024 * 1024, gzip: false }
}
};
class BudgetChecker {
constructor(distPath, budget) {
this.distPath = distPath;
this.budget = budget;
this.results = [];
this.passed = true;
}
async check() {
console.log(chalk.blue('\n📊 性能预算检查\n'));
console.log(chalk.gray('─'.repeat(60)));
// 检查单个资源
await this.checkResources();
// 检查总量
await this.checkTotals();
// 输出结果
this.printResults();
return this.passed;
}
async checkResources() {
for (const [pattern, config] of Object.entries(this.budget.resources)) {
const files = this.findFiles(pattern);
for (const file of files) {
const size = await this.getSize(file, config.gzip);
const passed = size <= config.maxSize;
if (!passed) this.passed = false;
this.results.push({
type: 'resource',
name: path.relative(this.distPath, file),
size,
maxSize: config.maxSize,
gzip: config.gzip,
passed
});
}
}
}
async checkTotals() {
const categories = {
javascript: /\.js$/,
css: /\.css$/,
images: /\.(png|jpg|jpeg|gif|webp|avif|svg)$/i
};
const allFiles = this.findFiles('**/*');
for (const [category, regex] of Object.entries(categories)) {
const files = allFiles.filter(f => regex.test(f));
let totalSize = 0;
for (const file of files) {
const config = this.budget.totals[category];
totalSize += await this.getSize(file, config?.gzip || false);
}
const config = this.budget.totals[category];
if (config) {
const passed = totalSize <= config.maxSize;
if (!passed) this.passed = false;
this.results.push({
type: 'total',
name: `Total ${category}`,
size: totalSize,
maxSize: config.maxSize,
gzip: config.gzip,
passed
});
}
}
}
findFiles(pattern) {
const glob = require('glob');
return glob.sync(path.join(this.distPath, pattern));
}
async getSize(filePath, gzip) {
const content = fs.readFileSync(filePath);
return gzip ? await gzipSize(content) : content.length;
}
formatSize(bytes) {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
}
printResults() {
console.log(chalk.gray('\n资源检查结果:\n'));
const resourceResults = this.results.filter(r => r.type === 'resource');
const totalResults = this.results.filter(r => r.type === 'total');
// 单个资源
for (const result of resourceResults) {
const status = result.passed ? chalk.green('✓') : chalk.red('✗');
const sizeText = this.formatSize(result.size);
const budgetText = this.formatSize(result.maxSize);
const gzipLabel = result.gzip ? chalk.gray('(gzip)') : '';
const percentage = ((result.size / result.maxSize) * 100).toFixed(1);
const bar = this.progressBar(result.size, result.maxSize);
console.log(
`${status} ${result.name}`,
chalk.gray(`${sizeText} / ${budgetText} ${gzipLabel}`),
bar,
chalk.gray(`${percentage}%`)
);
}
console.log(chalk.gray('\n总量检查结果:\n'));
// 总量
for (const result of totalResults) {
const status = result.passed ? chalk.green('✓') : chalk.red('✗');
const sizeText = this.formatSize(result.size);
const budgetText = this.formatSize(result.maxSize);
console.log(
`${status} ${result.name}:`,
chalk.gray(`${sizeText} / ${budgetText}`)
);
}
// 总结
console.log(chalk.gray('\n' + '─'.repeat(60)));
const passedCount = this.results.filter(r => r.passed).length;
const totalCount = this.results.length;
if (this.passed) {
console.log(chalk.green(`\n✅ 所有检查通过 (${passedCount}/${totalCount})\n`));
} else {
console.log(chalk.red(`\n❌ 预算超标 (${passedCount}/${totalCount} 通过)\n`));
}
}
progressBar(current, max) {
const width = 20;
const percentage = Math.min(current / max, 1);
const filled = Math.round(width * percentage);
const empty = width - filled;
const color = percentage > 1 ? chalk.red :
percentage > 0.8 ? chalk.yellow : chalk.green;
return color('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
}
}
// 执行检查
const checker = new BudgetChecker('./dist', BUDGET);
checker.check().then(passed => {
process.exit(passed ? 0 : 1);
});
预算超标时的应对策略
分析超标原因
// 分析 Bundle 大小的常见问题
const bundleAnalysis = {
// 问题1:重复依赖
duplicateDependencies: {
detect: 'webpack-bundle-analyzer',
solution: `
// 使用 resolutions 强制统一版本
// package.json
{
"resolutions": {
"lodash": "^4.17.21"
}
}
`
},
// 问题2:全量引入库
fullImports: {
example: "import _ from 'lodash'",
solution: "import debounce from 'lodash/debounce'"
},
// 问题3:开发依赖打包
devDependenciesIncluded: {
detect: '检查 bundle 中是否有 test / mock / storybook 代码',
solution: '配置正确的 sideEffects 和 tree-shaking'
},
// 问题4:未压缩的资源
uncompressedResources: {
solution: '启用 gzip/brotli 压缩'
}
};
// Bundle 大小优化清单
const optimizationChecklist = [
{
category: '代码分割',
items: [
'路由级别代码分割',
'组件懒加载',
'第三方库单独打包'
]
},
{
category: '依赖优化',
items: [
'移除未使用的依赖',
'使用轻量替代品(如 date-fns 替代 moment)',
'Tree Shaking 确认生效'
]
},
{
category: '资源优化',
items: [
'图片使用 WebP/AVIF 格式',
'字体子集化',
'SVG 优化'
]
},
{
category: '构建优化',
items: [
'启用压缩(Terser/ESBuild)',
'移除 Source Maps(生产环境)',
'使用生产模式构建'
]
}
];
预算调整流程
预算超标处理流程:
┌───────────────┐
│ 预算超标报警 │
└───────┬───────┘
↓
┌───────────────────────────────────────┐
│ 1. 确认是否为预期变更 │
│ - 新功能导致的合理增长? │
│ - 还是意外引入的问题? │
└───────────────────┬───────────────────┘
↓
┌───────────────┴───────────────┐
↓ ↓
预期增长 意外问题
│ │
↓ ↓
┌───────────────────┐ ┌───────────────────┐
│ 2A. 评估是否值得 │ │ 2B. 调查根因 │
│ - ROI 分析 │ │ - 依赖分析 │
│ - 用户价值 │ │ - 代码审查 │
└─────────┬─────────┘ └─────────┬─────────┘
↓ ↓
┌─────────────────┐ ┌─────────────────┐
│ 3A. 调整预算 │ │ 3B. 修复问题 │
│ + 记录原因 │ │ + 防止复发 │
└─────────────────┘ └─────────────────┘
团队性能文化建设
建立性能意识
// 性能看板配置
const performanceDashboard = {
// 核心指标
keyMetrics: [
{ name: 'LCP', current: 2100, budget: 2500, trend: 'improving' },
{ name: 'FID', current: 85, budget: 100, trend: 'stable' },
{ name: 'CLS', current: 0.08, budget: 0.1, trend: 'improving' }
],
// Bundle 大小趋势
bundleTrend: {
labels: ['1月', '2月', '3月', '4月', '5月'],
data: [180, 195, 205, 198, 185], // KB
budget: 200
},
// 告警配置
alerts: {
// Slack 通知
slack: {
channel: '#performance',
triggers: ['budget-exceeded', 'score-drop']
},
// 邮件通知
email: {
recipients: ['dev-team@company.com'],
frequency: 'weekly'
}
}
};
代码审查清单
## 性能审查清单
### Bundle 大小
- [ ] 新依赖是否必要?有更轻量的替代品吗?
- [ ] 是否使用了按需加载?
- [ ] 是否有重复的依赖?
### 运行时性能
- [ ] 是否有不必要的重渲染?
- [ ] 是否有内存泄漏风险?
- [ ] 事件监听器是否正确清理?
### 资源加载
- [ ] 图片是否优化?格式是否现代化?
- [ ] 是否使用了懒加载?
- [ ] 关键资源是否预加载?
### 网络请求
- [ ] API 请求是否合理?是否可以合并?
- [ ] 是否有不必要的第三方脚本?
最佳实践总结
性能预算最佳实践:
制定阶段:
✓ 基于业务目标设定预算,不要凭感觉
✓ 分析竞争对手,确保有竞争力
✓ 考虑目标用户的设备和网络条件
✓ 预留 10-20% 的缓冲空间
实施阶段:
✓ 集成到 CI/CD 流程中自动检查
✓ 超出预算时阻止部署或发出告警
✓ 使用多种工具交叉验证
✓ 定期审查和调整预算
维护阶段:
✓ 每季度回顾预算是否合理
✓ 随着业务发展调整预算
✓ 记录每次预算调整的原因
✓ 建立性能报告和趋势追踪
团队协作:
✓ 让全团队了解性能预算
✓ 在代码审查中检查性能影响
✓ 庆祝性能改进,关注性能回归
✓ 建立性能冠军/负责人制度
性能预算不是限制,而是保护。它帮助团队在快速迭代中保持性能底线,让用户始终获得良好的体验。


