初识预加载

129 阅读4分钟

 背景: 

在实习项目中,我遇到了一个典型的视频加载性能问题。页面上有三四个十几秒的视频,只有在满足特定条件时才会播放。最初没有做任何预加载,结果播放时偶尔会出现卡顿。于是我在 <video> 标签中添加了 rel="prefetch" 属性,但由于这些视频共用同一个标签、仅动态切换 src,实际效果并不明显。后来我尝试在组件挂载时通过 JavaScript 手动预加载,但在低速网络下首屏渲染依旧很差。经过查阅 MDN 文档并向 ChatGPT 请教,学习总结了一下预加载。(文末附本人的解决方案)

一、什么是“预加载”(Preloading)

预加载指的是在资源还未被使用前提前加载进浏览器缓存或内存,以便在真正需要时能瞬间可用
核心目标:减少用户感知的等待时间(Perceived Latency)

浏览器的下载队列和渲染主线程都是有限资源,预加载的设计哲学就是:

“先把带宽用在未来要用的内容上,但不要妨碍当前页面渲染。”


 二、requestIdleCallback 的本质

requestIdleCallback(callback[, options])
是一个任务调度 API,允许开发者在浏览器空闲帧期间执行非关键逻辑。

浏览器每 16.6ms(约 60FPS)会重新绘制一次。
当主线程完成布局、绘制、事件处理后,若本帧仍有空闲时间,就会执行 requestIdleCallback 注册的回调。

requestIdleCallback(deadline => {
  while (deadline.timeRemaining() > 0) {
    // 执行轻量任务
  }
})

关键点:

  •  不保证立即执行(由浏览器调度)
  •  可通过 deadline.timeRemaining() 判断剩余时间
  •  低优先级任务(浏览器可能跳过)
  •  适用于“可延迟的预加载逻辑”

换句话说:
requestIdleCallback 是**“让预加载不干扰主线程的调度器”**,不是预加载本身。


 三、预加载的四种核心方式(+ 浏览器底层优先级)

方法优先级触发时机适用场景浏览器行为
<link rel="preload">立即加载首屏关键资源主线程立刻发起请求,占带宽
<link rel="prefetch">空闲时加载可能会用到的下个页面资源放入低优先级下载队列
JS 手动加载 (new Image(), video.load())可控精确控制加载逻辑由 JS 主动创建请求
requestIdleCallback()调度层空闲帧执行调度非关键任务(如上报、缓存)不加载资源,仅负责“何时执行”

 四、组合优化策略(实战建议)

// 定义资源
const prefetchVideo = () => {
  const link = document.createElement('link')
  link.rel = 'prefetch'
  link.as = 'video'
  link.href = '/assets/intro.mp4'
  document.head.appendChild(link)
}

// 调度执行
if ('requestIdleCallback' in window) {
  requestIdleCallback(prefetchVideo)
} else {
  // Safari fallback
  setTimeout(prefetchVideo, 2000)
}

组合优势:

  • prefetch → 利用空闲带宽;
  • requestIdleCallback → 避免主线程忙碌时添加 <link>
  • 整体节奏平衡:CPU 不阻塞,网络不浪费。

 五、HTTP 层与浏览器调度机制

浏览器内部有资源优先级系统,会根据资源类型分配网络带宽:

类型优先级
HTML / CSS / JS (首屏)Highest
preloadHigh
prefetchLow
img / video / font (非首屏)Medium / Low

在 HTTP/2 / HTTP/3 下,preload 可以直接让服务端提前推送(Server Push 已废弃但仍有影响),
prefetch 只是提示浏览器:“这资源可能未来用到”,是否加载由浏览器决定。


 六、更多高级预加载技巧

  1. DNS Prefetch / Preconnect
    提前建立 DNS 或 TCP/TLS 连接,加速外部资源。
<link rel="dns-prefetch" href="//cdn.example.com">
  <link rel="preconnect" href="https://cdn.example.com">

  1. 模块预加载(ESM 动态导入)
import(/* webpackPrefetch: true */ './nextPage.js')

Webpack 自动生成 <link rel="prefetch">,用于懒加载页面的提前下载。

  1. Service Worker 缓存预加载
    可以结合 Cache API,在空闲时缓存页面资源:
self.addEventListener('message', e => {
  caches.open('v1').then(cache => cache.addAll(['/next.html', '/next.js']))
})


 七、总结(逻辑关系图)

预加载 = 提前加载 + 合理调度
└── preload:立即加载,关键资源
└── prefetch:未来资源,低优先级
└── JS手动加载:精准控制加载时机
└── requestIdleCallback:优化执行时机(CPU层)

底层机制:

  • 浏览器有独立的网络调度系统;
  • 各种 rel 属性影响资源优先级;
  • requestIdleCallback 属于 任务调度层,用于协调 CPU 资源;
  • 组合使用时,能最大化性能与体验。

八、解决方案

const preloadVideos = () => {
  const videoUrls = [
  //视频链接
];

  videoUrls.forEach((url) => {
    const link = document.createElement("link");
    link.rel = "prefetch";
    link.as = "video";
    link.href = url;
    document.head.appendChild(link);
  });
};

const scheduleVideoPreload = () => {
  if (typeof window === "undefined") return;
  const idleCallback = (window as any).requestIdleCallback;

  if (typeof idleCallback === "function") {
    idleCallback(() => {
      preloadVideos();
    });
  } else {
    requestAnimationFrame(() => {
      preloadVideos();
    });
  }
};
// 组件挂载时预加载视频
onMounted(() => {
  requestAnimationFrame(() => {
    scheduleVideoPreload();
  });
});

有更好的解决方案欢迎评论区交流。