一、成因分析
- 页面/组件初始化时并发发起大量接口请求
- 多个组件/页面重复请求同一数据
- 滚动、输入等高频事件触发频繁请求
- 用户操作过快导致重复提交
- 缓存未利用,数据每次都重新拉取
- 前端路由切换未做请求管理
二、优化方案与代码示例
1. 接口合并(API聚合)
- 后端提供聚合接口,一次请求返回多个数据,减少请求次数
后端PHP示例:
// /api/homepage
return [
'banners' => getBanners(),
'hot_articles' => getHotArticles(),
'user_info' => getUserInfo($userId),
];
前端调用:
fetch('/api/homepage').then(res => res.json()).then(data => {
// 一次性渲染多个模块
});
2. 前端请求去重/防抖/节流
请求去重(防止同一接口并发多次)
JS示例(简单版):
const pendingRequests = new Set();
function fetchOnce(url) {
if (pendingRequests.has(url)) return;
pendingRequests.add(url);
fetch(url)
.then(res => res.json())
.finally(() => pendingRequests.delete(url));
}
防抖(debounce)/节流(throttle)高频事件
防抖:
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
}
}
// 用于搜索输入框
input.addEventListener('input', debounce(function(e) {
fetch('/api/search?q=' + e.target.value);
}, 300));
节流:
function throttle(fn, delay) {
let last = 0;
return function(...args) {
const now = Date.now();
if (now - last > delay) {
last = now;
fn.apply(this, args);
}
}
}
// 用于滚动加载
window.addEventListener('scroll', throttle(loadMore, 200));
3. 前端数据缓存(本地/内存/全局状态)
- 全局状态管理:如Vuex、Redux、Pinia等,页面/组件间共享数据,避免重复请求
- 本地缓存:localStorage/sessionStorage/IndexedDB
- 内存缓存:请求结果存变量,下次直接用
示例:
let userInfoCache = null;
function getUserInfo() {
if (userInfoCache) return Promise.resolve(userInfoCache);
return fetch('/api/user').then(res => res.json()).then(data => {
userInfoCache = data;
return data;
});
}
4. 服务端缓存/接口合并
- 后端缓存热点数据,如Redis、APCu,减少数据库压力
- GraphQL:天然支持聚合查询
5. 请求合并(Batching)
- 多个请求合并为一个(如GraphQL、RESTful批量接口)
RESTful批量接口示例:
// /api/batch?urls=/api/user,/api/notice
$urls = explode(',', $_GET['urls']);
$result = [];
foreach ($urls as $url) {
$result[$url] = file_get_contents($url);
}
return $result;
6. 防止重复提交
- 按钮禁用、请求中标记、唯一请求ID等
示例:
let submitting = false;
function submitForm() {
if (submitting) return;
submitting = true;
fetch('/api/submit', {method: 'POST', body: ...})
.finally(() => submitting = false);
}
7. 路由切换时取消未完成请求
- 用AbortController(Fetch)、axios的cancelToken等
Fetch示例:
let controller = new AbortController();
fetch('/api/data', { signal: controller.signal });
// 路由切换时
controller.abort();
三、监控与排查
- Chrome DevTools Network:查看请求数量、重复请求
- APM/埋点:统计接口QPS、重复率
- 后端日志:分析高频/重复接口
四、实际优化流程举例
- 后端聚合接口,减少前端请求数
- 前端全局状态管理/本地缓存,避免重复拉取
- 高频事件用防抖/节流
- 请求去重、合并、批量处理
- 按钮禁用/唯一ID防止重复提交
- 监控和持续优化
五、总结
- 接口请求过多、重复,核心是聚合、缓存、去重、合并、限流。
- 前后端协作,持续优化,监控不可少。