前端性能优化:从"按下预加载"到"AI级智能预判系统"
文章目录
- 前端性能优化:从"按下预加载"到"AI级智能预判系统"
-
- 前言
- [一、基础方案:按下即预加载(active 预加载)](#一、基础方案:按下即预加载(active 预加载))
-
- [1.1 为什么用 `active` 而不是 `hover`?](#1.1 为什么用
active而不是hover?) - [1.2 核心实现代码](#1.2 核心实现代码)
- [1.3 防抖优化(防止误触)](#1.3 防抖优化(防止误触))
- [1.1 为什么用 `active` 而不是 `hover`?](#1.1 为什么用
- 二、进阶方案:用户行为记忆分析(localStorage)
-
- [2.1 数据采集模块](#2.1 数据采集模块)
- [2.2 综合评分算法](#2.2 综合评分算法)
- [2.3 智能预加载引擎](#2.3 智能预加载引擎)
- 三、终极方案:完整系统集成
- 四、使用方式
-
- [4.1 基础使用](#4.1 基础使用)
- [4.2 调试与查看](#4.2 调试与查看)
- [4.3 自定义配置](#4.3 自定义配置)
- 五、效果对比
- 六、注意事项
-
- [6.1 隐私保护](#6.1 隐私保护)
- [6.2 性能考虑](#6.2 性能考虑)
- [6.3 兼容性](#6.3 兼容性)
- 七、未来展望
- 八、总结
前言
你是否遇到过这样的场景:页面里有很多超链接,如果一上来全部加载完,性能爆炸、卡顿严重;但如果完全不管,用户点击后又要等半天,尤其是内容多的页面,体验极差。
本文将从最基础的 "按下即预加载" 方案开始,一步步演进到一套完整的 "基于用户行为分析的智能预加载系统",涵盖:行为采集、综合评分、空闲预加载、沉浸度分析等核心模块。
所有代码均可直接复制使用,建议收藏!
一、基础方案:按下即预加载(active 预加载)
1.1 为什么用 active 而不是 hover?
| 事件 | 触发频率 | 适用场景 |
|---|---|---|
hover |
鼠标划过每个链接都会触发 | 容易造成大量无效请求,浪费带宽 |
active |
只有用户按下鼠标时触发 | 精准命中用户真正想点的链接 |
PC 端用户浏览时,鼠标会无意识划过大量链接,用 hover 会触发大量无意义的预加载。而 active(mousedown 瞬间)才是用户真正"决定"点击的时刻,从按下到松开的 300ms~800ms 足够完成 DNS 解析 + 关键资源请求。
1.2 核心实现代码
javascript
// ============ 基础版:按下即预加载 ============
(function() {
// 预加载函数:通过 link prefetch 实现
function prefetch(url) {
if (!url) return;
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = url;
link.crossOrigin = 'anonymous';
document.head.appendChild(link);
console.log(`[预加载] 已触发: ${url}`);
}
// 监听所有 a 标签的 mousedown 事件
document.addEventListener('mousedown', function(e) {
const target = e.target.closest('a');
if (!target) return;
const href = target.getAttribute('href');
if (!href || href.startsWith('javascript:') || href.startsWith('#')) return;
// 只预加载同源链接(防止跨域问题)
if (href.startsWith('http') && !href.includes(window.location.hostname)) return;
prefetch(href);
});
// 移动端支持:touchstart
document.addEventListener('touchstart', function(e) {
const target = e.target.closest('a');
if (!target) return;
const href = target.getAttribute('href');
if (!href || href.startsWith('javascript:') || href.startsWith('#')) return;
if (href.startsWith('http') && !href.includes(window.location.hostname)) return;
prefetch(href);
});
})();
1.3 防抖优化(防止误触)
javascript
// ============ 进阶版:带防抖的预加载 ============
const prefetchQueue = new Map();
let prefetchTimer = null;
function prefetchWithDebounce(url) {
// 如果已经预加载过,直接返回
if (prefetchQueue.has(url)) return;
clearTimeout(prefetchTimer);
prefetchTimer = setTimeout(() => {
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = url;
document.head.appendChild(link);
prefetchQueue.set(url, true);
console.log(`[预加载] 已触发: ${url}`);
}, 150); // 延迟 150ms,防止快速划过多个链接
}
二、进阶方案:用户行为记忆分析(localStorage)
基础方案只能解决"当前点击"的加速问题。如果能让系统 "记住用户的浏览习惯",就能在用户点击之前就提前加载好 TA 最常访问的页面。
2.1 数据采集模块
javascript
// ============ 行为数据采集 ============
const PAGE_METRICS = {
enterTime: Date.now(),
lastScrollY: 0,
interactionCount: 0,
scrollDepth: 0
};
// 页面进入时记录时间
document.addEventListener('DOMContentLoaded', function() {
PAGE_METRICS.enterTime = Date.now();
PAGE_METRICS.lastScrollY = window.scrollY;
});
// 统计交互次数(点击、选择文本等)
document.addEventListener('click', function() {
PAGE_METRICS.interactionCount++;
});
document.addEventListener('selectionchange', function() {
if (window.getSelection().toString()) {
PAGE_METRICS.interactionCount++;
}
});
// 页面离开时保存数据
window.addEventListener('beforeunload', function() {
const url = window.location.href;
const duration = (Date.now() - PAGE_METRICS.enterTime) / 1000;
const scrollDepth = Math.max(
0,
Math.min(
100,
(window.scrollY + window.innerHeight) / document.documentElement.scrollHeight * 100
)
);
saveMetrics(url, {
duration: Math.round(duration),
scrollDepth: Math.round(scrollDepth),
interactionCount: PAGE_METRICS.interactionCount,
timestamp: Date.now()
});
});
// 存储到 localStorage
function saveMetrics(url, metrics) {
const history = JSON.parse(localStorage.getItem('prefetch_history') || '{}');
if (!history[url]) {
history[url] = { visits: [] };
}
history[url].visits.push(metrics);
// 只保留最近 10 次访问
if (history[url].visits.length > 10) {
history[url].visits.shift();
}
// 限制总页面数不超过 50 个
const keys = Object.keys(history);
if (keys.length > 50) {
// 按最后访问时间排序,删除最旧的
keys.sort((a, b) => {
const aLast = history[a].visits[history[a].visits.length - 1]?.timestamp || 0;
const bLast = history[b].visits[history[b].visits.length - 1]?.timestamp || 0;
return aLast - bLast;
});
delete history[keys[0]];
}
localStorage.setItem('prefetch_history', JSON.stringify(history));
}
2.2 综合评分算法
javascript
// ============ 综合评分算法 ============
function calculateScore(urlData) {
const visits = urlData.visits || [];
if (visits.length === 0) return 0;
// 取最近 5 次访问
const recent = visits.slice(-5);
// 1. 访问频率分(30%)
const freqScore = Math.min(visits.length / 10, 1) * 30;
// 2. 平均停留时长分(25%):超过 60 秒算优质
const avgDuration = recent.reduce((s, v) => s + v.duration, 0) / recent.length;
const durationScore = Math.min(avgDuration / 60, 1) * 25;
// 3. 平均滚动深度分(25%):超过 80% 说明内容吸引人
const avgDepth = recent.reduce((s, v) => s + v.scrollDepth, 0) / recent.length;
const depthScore = Math.min(avgDepth / 80, 1) * 25;
// 4. 交互活跃度分(20%)
const avgInteract = recent.reduce((s, v) => s + v.interactionCount, 0) / recent.length;
const interactScore = Math.min(avgInteract / 10, 1) * 20;
// 5. 时效性加成:最近访问越近加分越多
const lastVisit = recent[recent.length - 1]?.timestamp || 0;
const daysAgo = (Date.now() - lastVisit) / (1000 * 60 * 60 * 24);
const recencyBonus = Math.max(0, (30 - daysAgo) / 30) * 10;
return Math.round(freqScore + durationScore + depthScore + interactScore + recencyBonus);
}
2.3 智能预加载引擎
javascript
// ============ 智能预加载引擎 ============
function getTopPrefetchTargets(limit = 3) {
const history = JSON.parse(localStorage.getItem('prefetch_history') || '{}');
const scored = Object.entries(history)
.map(([url, data]) => ({
url,
score: calculateScore(data),
visits: data.visits || []
}))
.filter(item => item.score > 20) // 低于 20 分不预加载
.sort((a, b) => b.score - a.score)
.slice(0, limit);
return scored.map(item => item.url);
}
// 页面空闲时自动预加载
function idlePrefetch() {
if (!('requestIdleCallback' in window)) {
// 降级方案:页面加载完成后 3 秒执行
setTimeout(doPrefetch, 3000);
return;
}
requestIdleCallback(doPrefetch, { timeout: 5000 });
}
function doPrefetch() {
const targets = getTopPrefetchTargets(3);
if (targets.length === 0) return;
console.log('[智能预加载] 目标列表:', targets);
targets.forEach(url => {
// 避免重复预加载
if (document.querySelector(`link[rel="prefetch"][href="${url}"]`)) return;
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = url;
document.head.appendChild(link);
});
}
// 页面加载完成后启动
window.addEventListener('load', idlePrefetch);
三、终极方案:完整系统集成
将以上所有模块整合成一个完整的工具类:
javascript
// ============ 完整系统:SmartPrefetch ============
const SmartPrefetch = {
// 配置项
config: {
debounceDelay: 150, // 防抖延迟(ms)
minScore: 20, // 最低预加载分数
maxPages: 50, // 最多存储页面数
maxVisitsPerPage: 10, // 每页最多存储访问次数
prefetchLimit: 3, // 空闲时预加载数量
durationThreshold: 60, // 停留时长阈值(秒)
depthThreshold: 80, // 滚动深度阈值(%)
},
// 初始化
init() {
this.bindEvents();
this.startIdlePrefetch();
console.log('[SmartPrefetch] 系统已启动');
},
// 绑定事件
bindEvents() {
// 点击预加载
document.addEventListener('mousedown', (e) => {
this.handlePrefetchTrigger(e);
});
document.addEventListener('touchstart', (e) => {
this.handlePrefetchTrigger(e);
});
// 行为采集
document.addEventListener('DOMContentLoaded', () => {
this.startMetricsCollection();
});
window.addEventListener('beforeunload', () => {
this.saveCurrentPageMetrics();
});
},
// 处理预加载触发
handlePrefetchTrigger(e) {
const target = e.target.closest('a');
if (!target) return;
const href = target.getAttribute('href');
if (!href || href.startsWith('javascript:') || href.startsWith('#')) return;
if (href.startsWith('http') && !href.includes(window.location.hostname)) return;
// 使用防抖预加载
this.prefetchWithDebounce(href);
},
// 防抖预加载
prefetchWithDebounce(url) {
if (this._prefetchCache?.has(url)) return;
clearTimeout(this._debounceTimer);
this._debounceTimer = setTimeout(() => {
this.doPrefetch(url);
}, this.config.debounceDelay);
},
// 执行预加载
doPrefetch(url) {
if (!this._prefetchCache) this._prefetchCache = new Map();
if (this._prefetchCache.has(url)) return;
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = url;
document.head.appendChild(link);
this._prefetchCache.set(url, true);
console.log(`[SmartPrefetch] 预加载: ${url}`);
},
// 空闲预加载
startIdlePrefetch() {
const fn = () => {
const targets = this.getTopPages(this.config.prefetchLimit);
targets.forEach(url => this.doPrefetch(url));
};
if ('requestIdleCallback' in window) {
requestIdleCallback(fn, { timeout: 5000 });
} else {
setTimeout(fn, 3000);
}
},
// 获取 TOP 页面
getTopPages(limit) {
const history = JSON.parse(localStorage.getItem('prefetch_history') || '{}');
const scored = Object.entries(history)
.map(([url, data]) => ({
url,
score: this.calculateScore(data)
}))
.filter(item => item.score >= this.config.minScore)
.sort((a, b) => b.score - a.score)
.slice(0, limit);
return scored.map(item => item.url);
},
// 计算评分
calculateScore(urlData) {
const visits = urlData.visits || [];
if (visits.length === 0) return 0;
const recent = visits.slice(-5);
const freqScore = Math.min(visits.length / 10, 1) * 30;
const avgDuration = recent.reduce((s, v) => s + v.duration, 0) / recent.length;
const durationScore = Math.min(avgDuration / this.config.durationThreshold, 1) * 25;
const avgDepth = recent.reduce((s, v) => s + v.scrollDepth, 0) / recent.length;
const depthScore = Math.min(avgDepth / this.config.depthThreshold, 1) * 25;
const avgInteract = recent.reduce((s, v) => s + v.interactionCount, 0) / recent.length;
const interactScore = Math.min(avgInteract / 10, 1) * 20;
const lastVisit = recent[recent.length - 1]?.timestamp || 0;
const daysAgo = (Date.now() - lastVisit) / (1000 * 60 * 60 * 24);
const recencyBonus = Math.max(0, (30 - daysAgo) / 30) * 10;
return Math.round(freqScore + durationScore + depthScore + interactScore + recencyBonus);
},
// 指标采集相关...
startMetricsCollection() {
this._metrics = {
enterTime: Date.now(),
lastScrollY: window.scrollY,
interactionCount: 0
};
// 绑定交互统计
const incInteract = () => this._metrics.interactionCount++;
document.addEventListener('click', incInteract);
document.addEventListener('selectionchange', () => {
if (window.getSelection().toString()) incInteract();
});
},
saveCurrentPageMetrics() {
const url = window.location.href;
const duration = (Date.now() - this._metrics.enterTime) / 1000;
const scrollDepth = Math.max(0, Math.min(100,
(window.scrollY + window.innerHeight) / document.documentElement.scrollHeight * 100
));
const history = JSON.parse(localStorage.getItem('prefetch_history') || '{}');
if (!history[url]) history[url] = { visits: [] };
history[url].visits.push({
duration: Math.round(duration),
scrollDepth: Math.round(scrollDepth),
interactionCount: this._metrics.interactionCount,
timestamp: Date.now()
});
if (history[url].visits.length > this.config.maxVisitsPerPage) {
history[url].visits.shift();
}
// 限制总页面数
const keys = Object.keys(history);
if (keys.length > this.config.maxPages) {
keys.sort((a, b) => {
const aLast = history[a].visits[history[a].visits.length - 1]?.timestamp || 0;
const bLast = history[b].visits[history[b].visits.length - 1]?.timestamp || 0;
return aLast - bLast;
});
delete history[keys[0]];
}
localStorage.setItem('prefetch_history', JSON.stringify(history));
},
// 工具:清空历史
clearHistory() {
localStorage.removeItem('prefetch_history');
this._prefetchCache?.clear();
console.log('[SmartPrefetch] 历史数据已清空');
},
// 工具:查看统计
getStats() {
const history = JSON.parse(localStorage.getItem('prefetch_history') || '{}');
const result = {};
Object.entries(history).forEach(([url, data]) => {
result[url] = {
visits: data.visits.length,
score: this.calculateScore(data),
avgDuration: Math.round(
data.visits.reduce((s, v) => s + v.duration, 0) / data.visits.length
),
avgDepth: Math.round(
data.visits.reduce((s, v) => s + v.scrollDepth, 0) / data.visits.length
)
};
});
return result;
}
};
// ============ 启动系统 ============
// 页面加载完成后初始化
if (document.readyState === 'complete') {
SmartPrefetch.init();
} else {
window.addEventListener('load', () => SmartPrefetch.init());
}
// 暴露全局对象供调试
window.SmartPrefetch = SmartPrefetch;
四、使用方式
4.1 基础使用
直接将上面的完整代码复制到你的项目中,或者单独保存为 smart-prefetch.js 并引入:
html
<script src="smart-prefetch.js"></script>
无需任何额外配置,系统会自动运行。
4.2 调试与查看
在浏览器控制台中:
javascript
// 查看所有页面的统计数据
console.table(SmartPrefetch.getStats());
// 查看 TOP 3 预加载目标
console.log(SmartPrefetch.getTopPages(3));
// 清空历史数据(隐私保护)
SmartPrefetch.clearHistory();
4.3 自定义配置
javascript
// 修改配置(在 init 之前)
SmartPrefetch.config.debounceDelay = 200;
SmartPrefetch.config.prefetchLimit = 5;
SmartPrefetch.config.minScore = 30;
SmartPrefetch.init();
五、效果对比
| 方案 | 首屏加载 | 页面跳转 | 带宽消耗 | 智能化程度 |
|---|---|---|---|---|
| 无优化 | 正常 | 慢(需等待加载) | 低 | 无 |
| 全部预加载 | 极慢(卡顿) | 极快 | 极高 | 无 |
| hover 预加载 | 正常 | 快 | 中高(大量无效请求) | 低 |
| active 预加载 | 正常 | 快 | 低(精准) | 中 |
| 智能预加载(本方案) | 正常 | 极快(命中缓存) | 低(按需加载) | 高(自学习) |
六、注意事项
6.1 隐私保护
所有数据仅存储在用户本地的 localStorage 中,不会上传到服务器 。建议在页面中添加 "清除浏览记忆" 按钮:
html
<button onclick="SmartPrefetch.clearHistory()">清除浏览记忆</button>
6.2 性能考虑
- 每页最多存储 10 次访问记录,总页面数限制 50 个
- 使用
requestIdleCallback在浏览器空闲时执行预加载 prefetch的优先级较低,不会影响当前页面的加载
6.3 兼容性
prefetch支持:Chrome、Firefox、Edge、Safari 15+requestIdleCallback支持:Chrome 47+、Firefox 55+、Edge 79+- 不支持的环境会自动降级
七、未来展望
这套系统还可以继续迭代:
- 时段权重:工作日预加载办公类页面,周末预加载娱乐类页面
- 网络感知:4G/5G 下启用预加载,2G/省流模式自动关闭
- Service Worker 集成:将预加载的资源缓存到 Cache Storage,实现离线访问
- A/B 测试:对比开启/关闭智能预加载的用户留存率
八、总结
本文从最基础的 "按下即预加载" 出发,一步步构建了一套完整的 "基于用户行为分析的智能预加载系统":
active预加载 :精准命中用户意图,避免hover带来的无效请求- 行为数据采集:记录浏览时长、滚动深度、交互次数等指标
- 综合评分算法:多维度评估页面价值,筛选最优预加载目标
- 空闲预加载:利用浏览器空闲时间静默加载,不干扰用户操作
- 隐私保护:所有数据仅存本地,用户可一键清除
这套方案的核心思想是:不做无差别的预加载,而是像 AI 一样学习用户的浏览习惯,在合适的时机加载合适的内容。
如果觉得本文对你有帮助,欢迎点赞、收藏、转发!
如果你有更好的想法,欢迎在评论区交流讨论!