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

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

文章目录

前言

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

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

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


一、基础方案:按下即预加载(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+
  • 不支持的环境会自动降级

七、未来展望

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

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

八、总结

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

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

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


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

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

相关推荐
迅猛龙办公室16 分钟前
Python输出当前计算机的系统日期和时间
开发语言·前端·python
无名猿30 分钟前
C++ 查找算法:std::find 与容器成员 find 的性能差距
c++·性能优化·stl·标准库
小狼1545432 分钟前
浏览器扩展脚本为什么有时候不生效:注入时机、iframe 和单页路由,多多开票助手
前端·chrome
じòぴé南冸じょうげん1 小时前
油猴脚本突然发现变成灰色了,无法使用?页面不生效?刷新页面没反应?
前端
Csvn2 小时前
组合式 API(Composition API)
前端
火柴就是我4 小时前
Android 打包报错 25.0.3
android·前端
BD_Marathon4 小时前
消息对象中字段的说明
java·前端·python
八荒启·交互动画4 小时前
Web特效025—用 Canvas 2D 做Web特效的定义与边界:这支画笔能做到哪一步
前端·webgl·网页特效·八荒启-交互动画·八荒启
Ai-_Man4 小时前
您您这可以把Dola的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。用AI导出鸭,答案是可以的
开发语言·前端·人工智能·小程序
excel4 小时前
Nuxt 中使用 useHead 优化 SEO 与 GEO
前端