前端性能优化:从“按下预加载”到“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 一样学习用户的浏览习惯,在合适的时机加载合适的内容。


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

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

相关推荐
Csvn1 小时前
📦 CSS Container Queries 实战踩坑:5 个让你怀疑人生的隐藏陷阱
前端
大家的林语冰2 小时前
👍 Rust 为 Node 插上翅膀,反超 Bun 和 pnpm,一体化工具我也有!
前端·javascript·node.js
坐而论道_2 小时前
兼容低版本 Android:让现代 Web 应用在旧 WebView 上稳定运行
前端·面试
ssshooter2 小时前
Promise 和 async/await 被 try-catch 包裹时的行为差异
前端·javascript·面试
肖志-AI全栈2 小时前
从零讲透 Ajax:XHR、Fetch、JSON 序列化到异步编程全链路
前端·ajax·json
yyt3630458412 小时前
KlineChartQuant Tooltip 高频交互 200FPS 性能优化完整复盘
前端·经验分享·性能优化·typescript·vue·交互·chrome devtools
2601_963771373 小时前
WordPress SEO Guide: Boost Rankings Without Technical Jargon
前端·php
李顿波3 小时前
Chrome 扩展程序 MV3 架构组件概览
前端·chrome·浏览器插件
KingCodeHan3 小时前
ESLint 完全指南:从入门到精通
前端