初识预加载

背景:

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

一、什么是"预加载"(Preloading)

预加载 指的是在资源还未被使用前提前加载进浏览器缓存或内存 ,以便在真正需要时能瞬间可用

核心目标:减少用户感知的等待时间(Perceived Latency)

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

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


二、requestIdleCallback 的本质

requestIdleCallback(callback[, options])

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

浏览器每 16.6ms(约 60FPS)会重新绘制一次。

当主线程完成布局、绘制、事件处理后,若本帧仍有空闲时间,就会执行 requestIdleCallback 注册的回调。

javascript 复制代码
requestIdleCallback(deadline => {
  while (deadline.timeRemaining() > 0) {
    // 执行轻量任务
  }
})

关键点:

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

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


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

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

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

javascript 复制代码
// 定义资源
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
preload High
prefetch Low
img / video / font (非首屏) Medium / Low

在 HTTP/2 / HTTP/3 下,preload 可以直接让服务端提前推送(Server Push 已废弃但仍有影响),

prefetch 只是提示浏览器:"这资源可能未来用到",是否加载由浏览器决定。


六、更多高级预加载技巧

  1. DNS Prefetch / Preconnect
    提前建立 DNS 或 TCP/TLS 连接,加速外部资源。
ini 复制代码
<link rel="dns-prefetch" href="//cdn.example.com">
  <link rel="preconnect" href="https://cdn.example.com">
  1. 模块预加载(ESM 动态导入)
go 复制代码
import(/* webpackPrefetch: true */ './nextPage.js')

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

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

七、总结(逻辑关系图)

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

底层机制:

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

八、解决方案

ini 复制代码
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();
  });
});

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

相关推荐
执携2 小时前
Vue Router (匹配当前路由的链接和类名配置)
前端·javascript·vue.js
一枚前端小能手2 小时前
🏷️ HTML 属性参考 - 常用与全局属性的行为、兼容性与最佳实践
前端·javascript·html
Milian3 小时前
每日前端知识点(一):原型与原型链
javascript
wa的一声哭了3 小时前
hf中transformers库中generate的greedy_search
android·java·javascript·pytorch·深度学习·语言模型·transformer
花归去3 小时前
vue甘特图
前端·javascript·vue.js
李瑞丰_liruifengv3 小时前
使用 Claude Agent SDK 开发一个 Agent 原来这么简单
前端·javascript·agent
用户12039112947263 小时前
深入JavaScript数组:从内存模型到遍历性能,打造高性能代码的基石
javascript
驯狼小羊羔3 小时前
学习随笔-http和https有何区别
前端·javascript·学习·http·https
进击的野人3 小时前
JavaScript DOM操作与事件处理:从小兔鲜儿电商网站看现代前端开发实践
前端·javascript