前端性能优化:从“按下预加载”到“AI级智能预判系统”

前端性能优化:从"按下预加载"到"AI级智能预判系统"

文章目录

前言

你是否遇到过这样的场景:页面里有很多超链接,如果一上来全部加载完,性能爆炸、卡顿严重;但如果完全不管,用户点击后又要等半天,尤其是内容多的页面,体验极差。

本文将从最基础的 "按下即预加载" 方案开始,一步步演进到一套完整的 "基于用户行为分析的智能预加载系统",涵盖:行为采集、综合评分、空闲预加载、沉浸度分析等核心模块。

所有代码均可直接复制使用,建议收藏!


一、基础方案:按下即预加载(active 预加载)

1.1 为什么用 active 而不是 hover

事件 触发频率 适用场景
hover 鼠标划过每个链接都会触发 容易造成大量无效请求,浪费带宽
active 只有用户按下鼠标时触发 精准命中用户真正想点的链接

PC 端用户浏览时,鼠标会无意识划过大量链接,用 hover 会触发大量无意义的预加载。而 activemousedown 瞬间)才是用户真正"决定"点击的时刻,从按下到松开的 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+
  • 不支持的环境会自动降级

七、未来展望

这套系统还可以继续迭代:

  1. 时段权重:工作日预加载办公类页面,周末预加载娱乐类页面
  2. 网络感知:4G/5G 下启用预加载,2G/省流模式自动关闭
  3. Service Worker 集成:将预加载的资源缓存到 Cache Storage,实现离线访问
  4. A/B 测试:对比开启/关闭智能预加载的用户留存率

八、总结

本文从最基础的 "按下即预加载" 出发,一步步构建了一套完整的 "基于用户行为分析的智能预加载系统"

  1. active 预加载 :精准命中用户意图,避免 hover 带来的无效请求
  2. 行为数据采集:记录浏览时长、滚动深度、交互次数等指标
  3. 综合评分算法:多维度评估页面价值,筛选最优预加载目标
  4. 空闲预加载:利用浏览器空闲时间静默加载,不干扰用户操作
  5. 隐私保护:所有数据仅存本地,用户可一键清除

这套方案的核心思想是:不做无差别的预加载,而是像 AI 一样学习用户的浏览习惯,在合适的时机加载合适的内容。


如果觉得本文对你有帮助,欢迎点赞、收藏、转发!

如果你有更好的想法,欢迎在评论区交流讨论!

相关推荐
程序员黑豆6 分钟前
鸿蒙应用开发之@Styles 装饰器:定义可复用的组件样式
前端·harmonyos
程序员码歌8 分钟前
我全程用 AI开发了一款微信小游戏,上线了
android·前端·游戏开发
咩咩啃树皮10 分钟前
第52篇:前端工程化完整体系精讲——Vite构建、模块化、打包原理、规范体系、面试终极通关
前端·面试·职场和发展
妙码生花10 分钟前
从 PHP 到 AI + Golang,程序员自救转型手记(四十八):菜单规则管理实现
前端·后端·ai编程
FogLetter12 分钟前
当JavaScript学会“分身术”:异步编程的进化简史
前端·javascript·面试
hunterandroid32 分钟前
[鸿蒙从零到一] HarmonyOS 分布式能力与设备协同实战:从发现设备到任务闭环
前端
lichenyang4531 小时前
从图片创作到本地可复现的 AIGC 全栈闭环:AIGC Creative Studio 实践复盘
前端·后端
牧艺1 小时前
别急着 Vibe Coding:AI 三小时写完需求后,我为什么宁愿多花一天
前端·agent·vibecoding
java1234_小锋1 小时前
Vue3+Vite简介以及构建第一个HelloWorld实例
前端·javascript·vue.js·vite
前进的搬砖er1 小时前
1.cesium 的基础知识篇
前端