抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前端性能优化:请求瀑布现象详解

在前端性能优化中,请求瀑布现象(Request Waterfall)是一个常见但容易被忽视的性能瓶颈。它指的是多个网络请求按照依赖关系串行执行,形成类似瀑布的时序图,导致页面加载时间显著增加。

什么是请求瀑布现象

请求瀑布现象是指多个网络请求按照依赖链条依次执行,后一个请求必须等待前一个请求完成后才能开始,这种串行的请求模式会大大延长页面的整体加载时间。

典型的瀑布序列

1
2
3
4
5
6
7
8
9
HTML 请求     ████████

CSS 请求 ████████

字体请求 ████████

图片请求 ████████

API 请求 ████████

瀑布现象的常见成因

1. HTML 解析过程中的阻塞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html>
<head>
<!-- 阻塞渲染的 CSS -->
<link rel="stylesheet" href="/styles/main.css">

<!-- 阻塞解析的同步 JS -->
<script src="/js/analytics.js"></script>
</head>
<body>
<!-- 图片需要等待 HTML 解析完成才开始加载 -->
<img src="/images/hero.jpg" alt="Hero">

<!-- 异步脚本可能触发新的请求 -->
<script>
// 这个请求要等待 JS 执行才发起
fetch('/api/user-data')
.then(response => response.json())
.then(data => {
// 基于第一个请求结果发起第二个请求
return fetch(`/api/user-details/${data.userId}`);
});
</script>
</body>
</html>

2. CSS 中的资源依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
/* main.css */
@import url('components.css'); /* 需要额外请求 */

@font-face {
font-family: 'CustomFont';
/* 字体文件需要等待 CSS 解析完成才开始加载 */
src: url('/fonts/custom-font.woff2') format('woff2');
}

.hero {
/* 背景图片需要等待 CSS 解析完成才开始加载 */
background-image: url('/images/background.jpg');
}

3. JavaScript 中的串行请求

1
2
3
4
5
6
7
8
9
10
11
12
13
// 典型的串行请求模式
async function loadUserData() {
// 第一个请求
const user = await fetch('/api/user').then(r => r.json());

// 第二个请求依赖第一个请求的结果
const profile = await fetch(`/api/users/${user.id}/profile`).then(r => r.json());

// 第三个请求又依赖第二个请求的结果
const preferences = await fetch(`/api/users/${user.id}/preferences?theme=${profile.theme}`).then(r => r.json());

return { user, profile, preferences };
}

4. 动态资源加载

1
2
3
4
5
6
7
8
9
10
11
12
13
// 动态加载模块造成的瀑布
document.addEventListener('DOMContentLoaded', async () => {
// 需要等待 DOM 加载完成才开始
const { Chart } = await import('./chart.js');

// 图表库加载完成后才加载数据
const data = await fetch('/api/chart-data').then(r => r.json());

// 根据数据类型动态加载不同的图表插件
if (data.type === 'advanced') {
await import('./chart-plugins.js');
}
});

如何识别请求瀑布现象

1. 使用浏览器开发者工具

打开 Chrome DevTools 的 Network 面板:

1
2
3
4
5
6
7
8
时间轴视图示例:
0s 1s 2s 3s 4s 5s
│ │ │ │ │ │
├─ HTML ████
├─ CSS ████
├─ Font ████
├─ Image ████
├─ API ████

关键指标:

  • Waterfall 列:显示每个请求的时序关系
  • Initiator 列:显示请求的发起者
  • Total Time:整体加载时间

2. 性能分析工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 使用 Performance API 监控关键时间点
function measureCriticalResources() {
// 监控关键资源加载时间
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${entry.loadEnd - entry.loadStart}ms`);
}
});

observer.observe({ entryTypes: ['resource'] });

// 监控页面关键时间点
window.addEventListener('load', () => {
const timing = performance.timing;
console.log('关键时间点分析:');
console.log(`DNS 解析: ${timing.domainLookupEnd - timing.domainLookupStart}ms`);
console.log(`TCP 连接: ${timing.connectEnd - timing.connectStart}ms`);
console.log(`请求响应: ${timing.responseEnd - timing.requestStart}ms`);
console.log(`DOM 解析: ${timing.domContentLoadedEventStart - timing.responseEnd}ms`);
});
}

3. Lighthouse 性能审计

1
2
3
4
5
6
7
8
# 使用 Lighthouse CLI 进行性能分析
npx lighthouse https://your-site.com --output=json --output-path=./performance-report.json

# 关注以下指标:
# - First Contentful Paint (FCP)
# - Largest Contentful Paint (LCP)
# - Cumulative Layout Shift (CLS)
# - Time to Interactive (TTI)

优化策略和解决方案

1. 资源预加载优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html>
<head>
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="//api.example.com">
<link rel="dns-prefetch" href="//cdn.example.com">

<!-- 预连接到关键第三方域名 -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>

<!-- 预加载关键 CSS -->
<link rel="preload" href="/styles/critical.css" as="style">

<!-- 预加载关键字体 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>

<!-- 预加载关键图片 -->
<link rel="preload" href="/images/hero.jpg" as="image">

<!-- 预加载关键 JavaScript 模块 -->
<link rel="modulepreload" href="/js/app.js">

<!-- 实际引用关键 CSS -->
<link rel="stylesheet" href="/styles/critical.css">
</head>
<body>
<!-- 内容 -->
</body>
</html>

📋 预加载技术详解备注

preconnect、preload、modulepreload 的具体区别:

preconnect 的影响对比
  • 不加 preconnect

    1
    2
    3
    4
    5
    6
    1. 解析到资源链接
    2. DNS 解析 ⏱️ ~20-200ms
    3. TCP 连接建立 ⏱️ ~50-200ms
    4. TLS 握手 ⏱️ ~100-300ms
    5. 发送 HTTP 请求
    6. 接收响应数据
  • 加 preconnect

    1
    2
    3
    4
    5
    1. 预先完成 DNS + TCP + TLS  ✅ 提前处理
    2. 解析到资源链接时直接发送请求
    3. 接收响应数据

    性能提升:减少 170-700ms 连接建立时间
preload 的影响对比
  • 不加 preload

    1
    2
    3
    4
    5
    6
    时间轴:串行加载
    0ms HTML 解析
    100ms 解析到 CSS,开始下载
    400ms CSS 完成,解析到图片
    500ms 开始下载图片
    800ms 图片完成
  • 加 preload

    1
    2
    3
    4
    5
    6
    7
    时间轴:并行加载
    0ms HTML 解析
    10ms preload 触发,CSS 和图片同时下载
    100ms 解析到 CSS 时已在缓存 ⚡ 立即可用
    500ms 解析到图片时已在缓存 ⚡ 立即显示

    性能提升:消除串行等待,资源并行加载
modulepreload 的影响对比
  • 不加 modulepreload

    1
    2
    3
    4
    5
    6
    7
    8
    0ms    下载主模块
    200ms 主模块完成,遇到 import utils.js
    210ms 开始下载 utils.js
    400ms utils.js 完成,遇到 import api.js
    410ms 开始下载 api.js
    600ms api.js 完成,遇到 import components.js
    610ms 开始下载 components.js
    900ms 所有模块加载完成
  • 加 modulepreload

    1
    2
    3
    4
    5
    0ms    同时下载主模块和所有依赖模块
    200ms 主模块完成,所有 import 从缓存加载
    210ms 所有模块链立即可用 ⚡

    性能提升:从 900ms 减少到 210ms,提升 76%
实际测试对比结果
指标 无优化版本 优化版本 提升幅度
FCP 1200ms 450ms 62.5% ⬆️
LCP 2100ms 800ms 61.9% ⬆️
DOM加载 950ms 400ms 57.9% ⬆️
完全加载 2800ms 1200ms 57.1% ⬆️
最佳实践组合策略
1
2
3
4
5
6
7
8
9
10
11
<!-- 第三方资源:preconnect + preload -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Roboto" as="style">

<!-- 关键本地资源:preload -->
<link rel="preload" href="/styles/critical.css" as="style">
<link rel="preload" href="/images/hero.jpg" as="image">

<!-- ES 模块:modulepreload -->
<link rel="modulepreload" href="./main.js">
<link rel="modulepreload" href="./utils.js">

核心区别总结:

  • preconnect:提前建立连接,减少网络延迟
  • preload:提前下载资源,实现并行加载
  • modulepreload:提前下载模块,消除模块依赖链等待

使用这些技术可以将整体页面加载性能提升 50-70%。


2. CSS 优化策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 关键 CSS 内联 -->
<style>
/* 首屏关键样式直接内联,避免额外请求 */
.header { background: #fff; height: 60px; }
.hero { min-height: 400px; }
.loading { display: flex; justify-content: center; }
</style>

<!-- 非关键 CSS 异步加载 -->
<link rel="preload" href="/styles/non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles/non-critical.css"></noscript>

<!-- 避免 @import,使用 link 标签 -->
<link rel="stylesheet" href="/styles/components.css">
<link rel="stylesheet" href="/styles/layout.css">

3. JavaScript 并行加载优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 并行发起多个独立请求
async function loadUserDataParallel() {
// 同时发起多个请求
const [userResponse, settingsResponse, notificationsResponse] = await Promise.all([
fetch('/api/user'),
fetch('/api/settings'),
fetch('/api/notifications')
]);

// 并行解析响应
const [user, settings, notifications] = await Promise.all([
userResponse.json(),
settingsResponse.json(),
notificationsResponse.json()
]);

return { user, settings, notifications };
}

// 使用 Promise.allSettled 处理部分失败的情况
async function loadDataWithFallback() {
const results = await Promise.allSettled([
fetch('/api/critical-data'),
fetch('/api/optional-data'),
fetch('/api/enhancement-data')
]);

const criticalData = results[0].status === 'fulfilled' ? results[0].value : null;
const optionalData = results[1].status === 'fulfilled' ? results[1].value : null;
const enhancementData = results[2].status === 'fulfilled' ? results[2].value : null;

return { criticalData, optionalData, enhancementData };
}

4. 智能资源加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 根据连接速度动态调整加载策略
function adaptiveLoading() {
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;

if (connection) {
const { effectiveType, downlink } = connection;

// 根据网络状况调整策略
if (effectiveType === 'slow-2g' || effectiveType === '2g') {
// 弱网环境:只加载关键资源
loadCriticalResourcesOnly();
} else if (effectiveType === '3g') {
// 中速网络:渐进加载
loadProgressively();
} else {
// 快速网络:正常加载
loadAllResources();
}
}
}

// 关键资源优先加载
function loadCriticalResourcesOnly() {
return Promise.all([
loadCSS('/styles/critical.css'),
loadJS('/js/critical.js'),
preloadImage('/images/hero-small.jpg')
]);
}

// 渐进式加载
async function loadProgressively() {
// 第一批:关键资源
await loadCriticalResourcesOnly();

// 第二批:重要但非关键资源
setTimeout(() => {
Promise.all([
loadCSS('/styles/components.css'),
loadJS('/js/interactions.js')
]);
}, 100);

// 第三批:增强功能
setTimeout(() => {
Promise.all([
loadJS('/js/analytics.js'),
loadJS('/js/third-party.js')
]);
}, 500);
}

5. HTTP/2 Push 和多路复用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 服务端配置示例(Node.js)
const http2 = require('http2');

const server = http2.createSecureServer({
// SSL 配置
});

server.on('stream', (stream, headers) => {
if (headers[':path'] === '/') {
// 推送关键资源
stream.pushStream({ ':path': '/styles/critical.css' }, (err, pushStream) => {
if (!err) {
pushStream.respondWithFile('/path/to/critical.css');
}
});

stream.pushStream({ ':path': '/js/critical.js' }, (err, pushStream) => {
if (!err) {
pushStream.respondWithFile('/path/to/critical.js');
}
});

// 响应主页面
stream.respondWithFile('/path/to/index.html');
}
});

6. 缓存策略优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Service Worker 实现智能缓存
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then((cache) => {
// 预缓存关键资源
return cache.addAll([
'/',
'/styles/critical.css',
'/js/app.js',
'/images/logo.svg'
]);
})
);
});

self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
if (response) {
// 缓存命中,同时发起网络请求更新缓存
fetch(event.request).then((fetchResponse) => {
const responseClone = fetchResponse.clone();
caches.open('v1').then((cache) => {
cache.put(event.request, responseClone);
});
});
return response;
}

// 缓存未命中,发起网络请求
return fetch(event.request);
})
);
});

📋 Service Worker 和 Cache API 详解备注

Service Worker 中的 self 和 caches 详细说明:

self 是什么?

self 是 Service Worker 的全局对象,类似于浏览器主线程中的 window 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在不同环境中的全局对象:

// 🌐 浏览器主线程
console.log(window); // Window 对象
console.log(this === window); // true

// 👷 Web Worker 线程
console.log(self); // DedicatedWorkerGlobalScope
console.log(this === self); // true

// ⚙️ Service Worker 线程
console.log(self); // ServiceWorkerGlobalScope
console.log(this === self); // true
console.log(typeof window); // "undefined" - 没有 window 对象!
console.log(typeof document); // "undefined" - 没有 document 对象!
caches 在哪里定义的?

caches 是浏览器提供的内置 Web API,属于 Cache API 的一部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 🌐 主线程中(页面脚本)
if ('caches' in window) {
console.log('Cache API 可用');
caches.open('my-cache').then(cache => {
// 可以使用
});
}

// ⚙️ Service Worker 中
// caches 自动可用,无需检查
console.log(caches); // CacheStorage 对象
console.log(self.caches); // 同样的 CacheStorage 对象
console.log(caches === self.caches); // true
完整的 Service Worker 上下文示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// service-worker.js

// 🔍 查看 Service Worker 环境
console.log('Service Worker 环境信息:');
console.log('全局对象:', self);
console.log('缓存 API:', caches);
console.log('注册信息:', self.registration);

// 📦 安装事件
self.addEventListener('install', (event) => {
console.log('Service Worker 安装中...');

// event.waitUntil 告诉浏览器:"等这个 Promise 完成再继续"
event.waitUntil(
// caches 是浏览器提供的全局 API
caches.open('v1').then((cache) => {
console.log('缓存已打开');

// 预缓存关键资源
return cache.addAll([
'/',
'/styles/critical.css',
'/js/app.js',
'/images/logo.svg'
]);
}).then(() => {
console.log('资源预缓存完成');
}).catch(error => {
console.error('预缓存失败:', error);
})
);
});

// 🔄 激活事件
self.addEventListener('activate', (event) => {
console.log('Service Worker 激活中...');

event.waitUntil(
// 清理旧缓存
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== 'v1') {
console.log('删除旧缓存:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});

// 🌐 拦截网络请求
self.addEventListener('fetch', (event) => {
// 拦截所有网络请求
event.respondWith(
caches.match(event.request).then(response => {
if (response) {
console.log('从缓存获取:', event.request.url);
return response;
}

console.log('网络请求:', event.request.url);
return fetch(event.request);
})
);
});
主线程中注册 Service Worker
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// main.js (页面脚本)

if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('SW 注册成功:', registration);

// 主线程也可以使用 caches API
if ('caches' in window) {
caches.keys().then(cacheNames => {
console.log('当前缓存:', cacheNames);
});
}
})
.catch(error => {
console.log('SW 注册失败:', error);
});
});
}
Cache API 的详细用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 在 Service Worker 中使用 caches

// 1. 打开/创建缓存
caches.open('my-cache-v1').then(cache => {
console.log('缓存已打开');
});

// 2. 添加资源到缓存
caches.open('my-cache-v1').then(cache => {
// 方式1:批量添加
return cache.addAll([
'/index.html',
'/style.css',
'/script.js'
]);

// 方式2:单个添加
// return cache.add('/index.html');

// 方式3:手动 fetch 并缓存
// return fetch('/api/data').then(response => {
// return cache.put('/api/data', response);
// });
});

// 3. 从缓存获取资源
caches.match('/index.html').then(response => {
if (response) {
console.log('从缓存获取成功');
return response;
} else {
console.log('缓存中没有该资源');
return fetch('/index.html');
}
});

// 4. 列出所有缓存
caches.keys().then(cacheNames => {
console.log('所有缓存名称:', cacheNames);
// ['my-cache-v1', 'my-cache-v2', ...]
});

// 5. 删除缓存
caches.delete('old-cache').then(success => {
console.log('缓存删除', success ? '成功' : '失败');
});
兼容性检查和错误处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 检查 Service Worker 和 Cache API 支持
function checkSupport() {
// 检查 Service Worker 支持
if (!('serviceWorker' in navigator)) {
console.warn('当前浏览器不支持 Service Worker');
return false;
}

// 检查 Cache API 支持
if (!('caches' in window)) {
console.warn('当前浏览器不支持 Cache API');
return false;
}

return true;
}

// Service Worker 中的错误处理
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1')
.then(cache => {
return cache.addAll([
'/',
'/styles/critical.css',
'/js/app.js',
'/images/logo.svg'
]);
})
.catch(error => {
console.error('缓存资源失败:', error);
// 可以缓存一个离线页面作为后备
return caches.open('v1').then(cache => {
return cache.add('/offline.html');
});
})
);
});

总结对比表:

概念 定义 位置 作用
self Service Worker 的全局对象 Service Worker 线程 类似主线程的 window
caches Cache API 的入口点 浏览器内置 Web API 管理缓存存储
event.waitUntil() 延长事件生命周期 Service Worker 事件 确保异步操作完成

关键要点:

  • self 是 Service Worker 的全局上下文,没有 windowdocument
  • caches 是浏览器提供的 Web API,在主线程和 Service Worker 中都可用
  • Service Worker 运行在独立线程中,与主页面隔离
  • 使用 event.waitUntil() 确保异步缓存操作完成

7. 图片优化策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- 响应式图片预加载 -->
<link rel="preload" as="image" href="/images/hero-mobile.webp" media="(max-width: 768px)">
<link rel="preload" as="image" href="/images/hero-desktop.webp" media="(min-width: 769px)">

<!-- 使用现代图片格式和响应式加载 -->
<picture>
<source media="(max-width: 768px)" srcset="/images/hero-mobile.webp" type="image/webp">
<source media="(max-width: 768px)" srcset="/images/hero-mobile.jpg" type="image/jpeg">
<source media="(min-width: 769px)" srcset="/images/hero-desktop.webp" type="image/webp">
<source media="(min-width: 769px)" srcset="/images/hero-desktop.jpg" type="image/jpeg">

<img src="/images/hero-desktop.jpg" alt="Hero Image" loading="eager">
</picture>

<!-- 懒加载非关键图片 -->
<img src="/images/placeholder.jpg"
data-src="/images/content.jpg"
alt="Content Image"
loading="lazy"
class="lazy-load">

<!-- 注释:
1) 原生懒加载 loading="lazy" 相对于下面的SmartImageLoader、无需额外JS代码 ,当接近视野时浏览器决定何时加载图片
2) 立即加载 loading="eager"
-->

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 智能图片懒加载
class SmartImageLoader {
constructor() {
this.observer = null;
this.init();
}

init() {
if ('IntersectionObserver' in window) {
this.observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
// 图片进入视野
if (entry.isIntersecting) {
this.loadImage(entry.target);
this.observer.unobserve(entry.target);
}
});
}, {
rootMargin: '50px' // 提前50px开始加载
});

// 找到所有需要懒加载的图片,开始观察
document.querySelectorAll('.lazy-load').forEach((img) => {
this.observer.observe(img);
});
}
}

loadImage(img) {
// 创建新的图片对象预加载
const imageLoader = new Image();
imageLoader.onload = () => {
img.src = img.dataset.src; // 替换为真实图片地址
img.classList.add('loaded'); // 添加loaded加载完成样式
};
imageLoader.src = img.dataset.src; // 开始加载图片
}
}

new SmartImageLoader();

实际案例分析

案例1:电商网站首页优化

优化前的瀑布序列:

1
2
3
4
5
6
7
8
9
10
HTML (500ms)        ████████

CSS (300ms) ████████

Fonts (400ms) ████████

Hero Image (600ms) ████████

Product API (350ms) ████████
总计:2.15秒

优化后的并行加载:

1
2
3
4
5
6
HTML (500ms)        ████████
CSS (预加载) ████████
Fonts (预加载) ████████
Hero Image (预加载) ████████
Product API (并行) ████████████
总计:0.85秒(60% 性能提升)

案例2:SPA 应用路由优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 优化前:串行加载
async function loadRoute(route) {
const component = await import(`./components/${route}.js`);
const data = await fetch(`/api/${route}`).then(r => r.json());
const styles = await loadCSS(`/styles/${route}.css`);

return { component, data, styles };
}

// 优化后:并行加载 + 预加载
class RouteLoader {
constructor() {
this.cache = new Map();
this.preloadOnHover();
}

async loadRoute(route) {
// 检查缓存
if (this.cache.has(route)) {
return this.cache.get(route);
}

// 并行加载所有资源
const [component, data, styles] = await Promise.all([
import(`./components/${route}.js`),
fetch(`/api/${route}`).then(r => r.json()),
this.loadCSS(`/styles/${route}.css`)
]);

const result = { component, data, styles };
this.cache.set(route, result);
return result;
}

// 在链接悬停时预加载
preloadOnHover() {
document.addEventListener('mouseover', (e) => {
const link = e.target.closest('[data-route]');
if (link) {
const route = link.dataset.route;
this.preloadRoute(route);
}
});
}

preloadRoute(route) {
if (!this.cache.has(route)) {
this.loadRoute(route);
}
}

loadCSS(href) {
return new Promise((resolve) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
link.onload = resolve;
document.head.appendChild(link);
});
}
}

监控和持续优化

1. 性能监控指标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 自定义性能监控
class PerformanceMonitor {
constructor() {
this.metrics = {};
this.init();
}

init() {
// 监控 CLS (Cumulative Layout Shift)
this.observeCLS();

// 监控 LCP (Largest Contentful Paint)
this.observeLCP();

// 监控 FID (First Input Delay)
this.observeFID();

// 监控资源加载时间
this.observeResourceTiming();
}

observeCLS() {
let clsValue = 0;
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
this.metrics.cls = clsValue;
}).observe({ type: 'layout-shift', buffered: true });
}

observeLCP() {
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
this.metrics.lcp = lastEntry.startTime;
}).observe({ type: 'largest-contentful-paint', buffered: true });
}

observeFID() {
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
this.metrics.fid = entry.processingStart - entry.startTime;
}
}).observe({ type: 'first-input', buffered: true });
}

observeResourceTiming() {
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
// 识别瀑布依赖关系
if (entry.initiatorType === 'css' || entry.initiatorType === 'script') {
console.warn(`潜在瀑布依赖: ${entry.name}`);
}
}
}).observe({ type: 'resource', buffered: true });
}

// 发送监控数据
sendMetrics() {
navigator.sendBeacon('/api/performance', JSON.stringify(this.metrics));
}
}

// 页面卸载时发送数据
window.addEventListener('beforeunload', () => {
monitor.sendMetrics();
});

2. A/B 测试性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 性能优化 A/B 测试
class PerformanceABTest {
constructor() {
this.variant = this.getVariant();
this.applyOptimization();
}

getVariant() {
// 50/50 分组
return Math.random() < 0.5 ? 'control' : 'optimized';
}

applyOptimization() {
if (this.variant === 'optimized') {
// 应用优化策略
this.enableResourcePreloading();
this.enableParallelRequests();
this.optimizeImageLoading();
}
}

enableResourcePreloading() {
// 预加载关键资源
const preloadLinks = [
{ href: '/styles/critical.css', as: 'style' },
{ href: '/js/app.js', as: 'script' },
{ href: '/fonts/main.woff2', as: 'font' }
];

preloadLinks.forEach(({ href, as }) => {
const link = document.createElement('link');
link.rel = 'preload';
link.href = href;
link.as = as;
if (as === 'font') link.crossOrigin = 'anonymous';
document.head.appendChild(link);
});
}

trackPerformance() {
window.addEventListener('load', () => {
const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart;

// 发送 A/B 测试数据
fetch('/api/ab-test', {
method: 'POST',
body: JSON.stringify({
variant: this.variant,
loadTime: loadTime,
userAgent: navigator.userAgent
})
});
});
}
}

总结

请求瀑布现象是影响前端性能的重要因素,通过合理的优化策略可以显著提升页面加载速度:

核心优化原则

  1. 并行优于串行:尽可能让资源并行加载
  2. 预加载关键资源:提前加载首屏需要的资源
  3. 智能缓存策略:减少重复请求
  4. 渐进式加载:优先加载关键内容

关键技术手段

  • Resource Hints (preload, prefetch, preconnect)
  • HTTP/2 多路复用和服务端推送
  • Service Worker 缓存优化
  • 代码分割和动态导入
  • 图片懒加载和响应式加载

监控和优化流程

  1. 使用开发者工具识别瀑布现象
  2. 分析关键渲染路径
  3. 应用相应的优化策略
  4. 持续监控性能指标
  5. A/B 测试验证优化效果

通过系统性地解决请求瀑布问题,可以将页面加载时间减少50%以上,显著提升用户体验。

评论