本文档总结了在浏览器 Console 中直接执行的性能分析脚本,用于诊断前端性能问题。
目录#
1. 请求统计分析#
1.1 按文件类型统计请求#
// 获取所有请求并按类型分类
const entries = performance.getEntriesByType("resource");
const stats = entries.reduce((acc, e) => {
const url = new URL(e.name);
let type = "other";
if (url.pathname.endsWith(".vue")) type = "vue";
else if (url.pathname.endsWith(".js")) type = "js";
else if (url.pathname.endsWith(".ts")) type = "ts";
else if (url.pathname.endsWith(".css")) type = "css";
else if (
url.pathname.includes("node_modules") ||
url.pathname.includes(".vite")
)
type = "node_modules";
acc[type] = (acc[type] || 0) + 1;
return acc;
}, {});
console.table(stats);
console.log("总请求数:", entries.length);1.2 按目录分类统计#
// 按目录路径统计请求
const entries = performance.getEntriesByType("resource");
const pathStats = entries.reduce((acc, e) => {
const url = new URL(e.name);
const path = url.pathname;
let category;
if (path.includes("node_modules") || path.includes(".vite/deps")) {
category = "📦 node_modules";
} else if (path.includes("/src/views/")) {
category = "📄 src/views (页面)";
} else if (path.includes("/src/components/")) {
category = "🧩 src/components (组件)";
} else if (path.includes("/src/")) {
category = "📁 src/other";
} else {
category = "❓ other";
}
acc[category] = (acc[category] || 0) + 1;
return acc;
}, {});
console.table(pathStats);1.3 查看具体的 node_modules 请求#
// 查看哪些 node_modules 请求没有被预构建
performance
.getEntriesByType("resource")
.filter((e) => e.name.includes("node_modules") || e.name.includes(".vite"))
.map((e) => new URL(e.name).pathname)
.forEach((p) => console.log(p));2. HAR 文件分析#
2.1 导出 HAR 文件#
- 打开 DevTools → Network 面板
- 刷新页面,等待所有请求加载完成
- 右键 → Save all as HAR with content
- 保存文件
2.2 分析 HAR 数据#
将 HAR 文件内容粘贴到变量 harData 中,然后执行:
// 假设 harData 是你的 HAR JSON 数据
// const harData = { ... };
const entries = harData.log.entries;
// 1. 总请求统计
console.log("📊 总请求数:", entries.length);
// 2. 按文件类型统计
const typeStats = {};
entries.forEach((e) => {
const url = e.request.url;
let type = "other";
if (url.includes(".js") || url.includes("type=module")) type = "js";
else if (url.includes(".vue")) type = "vue";
else if (url.includes(".css")) type = "css";
else if (url.includes(".ts")) type = "ts";
typeStats[type] = (typeStats[type] || 0) + 1;
});
console.log("\n📁 按文件类型:");
console.table(typeStats);
// 3. 按路径分类统计
const pathStats = {};
entries.forEach((e) => {
const url = e.request.url;
let category;
if (url.includes(".vite/deps")) category = "📦 .vite/deps (预构建)";
else if (url.includes("node_modules"))
category = "📦 node_modules (未预构建)";
else if (url.includes("/src/views/")) category = "📄 src/views";
else if (url.includes("/src/components/")) category = "🧩 src/components";
else if (url.includes("/src/")) category = "📁 src/other";
else category = "❓ other";
pathStats[category] = (pathStats[category] || 0) + 1;
});
console.log("\n📂 按目录分类:");
console.table(pathStats);
// 4. 找出请求最多的包(关键!)
const packageStats = {};
entries.forEach((e) => {
const url = e.request.url;
const match = url.match(/node_modules\/(@[^\/]+\/[^\/]+|[^\/]+)/);
if (match) {
const pkg = match[1];
packageStats[pkg] = (packageStats[pkg] || 0) + 1;
}
});
const sortedPackages = Object.entries(packageStats)
.sort((a, b) => b[1] - a[1])
.slice(0, 20);
console.log("\n🔥 请求最多的包 TOP 20:");
console.table(
sortedPackages.map(([pkg, count]) => ({ 包名: pkg, 请求数: count })),
);3. 性能指标测量#
3.1 一键获取所有性能指标#
(() => {
const nav = performance.getEntriesByType("navigation")[0];
const paint = performance.getEntriesByType("paint");
const resources = performance.getEntriesByType("resource");
const fp = paint.find((p) => p.name === "first-paint");
const fcp = paint.find((p) => p.name === "first-contentful-paint");
console.log("=== 📊 性能指标 ===");
console.log(`⚪ 白屏时间 (First Paint): ${fp?.startTime.toFixed(2)}ms`);
console.log(`📝 首次内容渲染 (FCP): ${fcp?.startTime.toFixed(2)}ms`);
console.log(`📄 DOM 加载完成: ${nav.domContentLoadedEventEnd.toFixed(2)}ms`);
console.log(`✅ 页面完全加载: ${nav.loadEventEnd.toFixed(2)}ms`);
console.log(`📦 资源请求数: ${resources.length}`);
console.log(
`📊 JS 请求数: ${resources.filter((r) => r.initiatorType === "script").length}`,
);
})();3.2 详细的导航性能指标#
const navigation = performance.getEntriesByType("navigation")[0];
const paintMetrics = performance.getEntriesByType("paint");
console.table({
"DNS 查询": navigation.domainLookupEnd - navigation.domainLookupStart,
"TCP 连接": navigation.connectEnd - navigation.connectStart,
"DOM 解析完成": navigation.domContentLoadedEventEnd - navigation.fetchStart,
页面完全加载: navigation.loadEventEnd - navigation.fetchStart,
});
paintMetrics.forEach((paint) => {
console.log(`${paint.name}: ${paint.startTime.toFixed(2)}ms`);
});3.3 资源加载时间分析#
// 找出加载最慢的资源
const resources = performance.getEntriesByType("resource");
const slowResources = resources
.map((r) => ({
name: r.name.split("/").pop().substring(0, 50),
duration: r.duration.toFixed(2) + "ms",
size: (r.transferSize / 1024).toFixed(2) + "KB",
}))
.sort((a, b) => parseFloat(b.duration) - parseFloat(a.duration))
.slice(0, 10);
console.log("🐢 加载最慢的资源 TOP 10:");
console.table(slowResources);4. 实战案例#
4.1 诊断 Vite 开发模式请求过多问题#
问题:首次加载有 1000+ 个 JS 请求
诊断步骤:
- 使用 HAR 分析脚本,发现
src/views有 585 个请求 - 定位到
import.meta.glob('../**/**/*.vue')范围过大 - 发现
result().then()立即执行导致所有组件被预加载
解决方案:
// 优化前:立即执行
if (result) result().then((mod) => (mod.default.name = path));
return result;
// 优化后:延迟执行
return () =>
result().then((mod) => {
mod.default.name = path;
return mod;
});优化效果:
- 请求数:1101 → 282(减少 74%)
4.2 检查 optimizeDeps 是否生效#
// 统计预构建 vs 未预构建的请求
const entries = performance.getEntriesByType("resource");
const viteDepStats = {
"已预构建 (.vite/deps)": 0,
"未预构建 (node_modules)": 0,
};
entries.forEach((e) => {
if (e.name.includes(".vite/deps")) {
viteDepStats["已预构建 (.vite/deps)"]++;
} else if (e.name.includes("node_modules")) {
viteDepStats["未预构建 (node_modules)"]++;
}
});
console.table(viteDepStats);附录:Network 面板筛选技巧#
| 筛选条件 | 输入内容 |
|---|---|
| 只看 JS | .js 或点击 JS 按钮 |
| 只看 Vue | .vue |
| 只看某路径 | /src/views/ |
| 排除某路径 | -node_modules |
| 精确 MIME | mime-type:application/javascript |
| 只看本地 | domain:localhost |
