🚀 懒加载与预加载:前端性能优化的两大核心策略

📅 发布于 2026年1月 | 👤 作者:博主 | 🏷️ 标签:懒加载, 预加载, 性能优化, React.lazy, IntersectionObserver, Web开发, 前端, 面试

欢迎来到我的博客文章!所有文章都是满满的前端干货,文章简明扼要。

📋 核心理念

懒加载(Lazy Loading)和预加载(Preloading)是前端性能优化的两大核心策略,它们像「按需供应」与「提前备货」的关系

一、核心概念对比

策略 目标 时机 适用资源 风险
懒加载
(Lazy Loading)
✅ 减少首屏负载 用户需要时才加载
(如滚动、点击、路由切换)
非首屏图片、组件、路由、重型库
(如图表、PDF)
⚠️ 加载延迟导致 UX 卡顿
(需优化 loading 状态)
预加载
(Preloading)
✅ 提前准备高概率资源 页面空闲时 or 关键资源加载后 下一页面、hover 后大概率点击的组件、关键字体/JS ⚠️ 浪费带宽
(预加载了用户没用到的资源)
最佳实践:懒加载是基线,预加载是增强
例如:首页 → 懒加载「个人中心」路由;当用户 hover「个人中心」按钮时 → 预加载该路由代码。

二、浏览器原生支持机制

现代浏览器提供了声明式 API,让预/懒加载更可控:

技术 说明 兼容性
<link rel="preload"> 高优先级预加载当前页面关键资源(如首屏字体、核心 JS) ✅ 全面支持
<link rel="prefetch"> 低优先级预加载未来可能用到的资源(如下一页 JS) ✅ 全面支持
IntersectionObserver 监听元素进入视口 → 触发懒加载(图片、组件) ✅ 现代浏览器
import() + Webpack/Vite 动态导入 → 实现 JS 懒加载 ✅ 构建工具自动处理

三、懒加载(Lazy Loading)实战示例

1. 图片懒加载(推荐用原生 loading="lazy")

<!-- 浏览器原生支持(无需 JS) -->
<img src="placeholder.jpg"
     data-src="real-image.jpg"
     loading="lazy"
     alt="描述"
     width="400"
     height="300"/>
✅ 优势:减少首屏请求数,提升 LCP(最大内容绘制)

2. 组件懒加载(React + Suspense)

// 懒加载重型组件(如报表图表)
const ChartComponent = React.lazy(() =>
  import(/* webpackChunkName: "chart" */ '@/components/Chart')
);

function Dashboard() {
  const [showChart, setShowChart] = useState(false);

  return (
    <div>
      <button onClick={() => setShowChart(true)}>显示图表</button>
      {showChart && (
        <Suspense fallback={<SkeletonChart />}>
          <ChartComponent />
        </Suspense>
      )}
    </div>
  );
}

3. 路由懒加载(React Router / Vue Router)

// React Router v6
const routes = [{
  path: '/profile',
  element: (
    <Suspense fallback={<Spinner />}>
      <Profile />
    </Suspense>
  ),
  // ✅ 动态导入
  loader: () => import('./routes/profile.loader'),
}];

// Vue 3
const routes = [{
  path: '/settings',
  component: () => import('@/views/Settings.vue') // 自动 code split
}];

🚀 四、预加载(Preloading)实战示例

✅ 1. 声明式预加载(HTML <link>)

<head>
  <!-- 高优先级:当前页关键资源 -->
  <link rel="preload" href="/fonts/brand.woff2"
        as="font" type="font/woff2" crossorigin>
  <link rel="preload" href="/main.js" as="script">

  <!-- 低优先级:未来可能用到的资源 -->
  <link rel="prefetch" href="/profile.js" as="script">
  <link rel="prefetch" href="/api/user-data.json"
        as="fetch" crossorigin>
</head>
rel 优先级 用途
preload ⭐⭐⭐⭐ 高 当前页面必须的资源(阻塞渲染的)
prefetch ⭐ 低 用户可能访问的资源(空闲时加载)

2. JS 动态预加载(用户行为预测)

// 场景:用户 hover 导航菜单 → 预加载目标页面
const prefetchCache = new Set<string>();

function prefetchRoute(path: string) {
  if (prefetchCache.has(path)) return;

  // ✅ 动态插入 <link rel="prefetch">
  const link = document.createElement('link');
  link.rel = 'prefetch';
  link.as = 'script';
  link.href = `/assets/${path}.js`; // 根据路由映射 chunk 名
  document.head.appendChild(link);

  prefetchCache.add(path);
}

// 使用
navItems.forEach(item => {
  item.addEventListener('mouseenter', () => {
    prefetchRoute(item.dataset.route!);
  });
});

🔧 进阶:结合 SWR 实现「数据 + 代码」并行预加载

// hover 时预加载数据 + 代码
button.onmouseenter = async () => {
  // 1. 预加载组件代码
  import('@/pages/Dashboard').catch(() => {});

  // 2. 预请求数据(SWR 自动缓存)
  await swrCache.revalidate('/api/dashboard-data');
};

3. 路由级预加载(React Router v6.4+)

// 使用 useNavigation 或 loader 预加载
function NavItem({ to, children }: { to: string; children: ReactNode }) {
  const { isLoading } = useNavigation();

  // 悬停时预加载目标路由
  const handleMouseEnter = () => {
    startTransition(() => {
      // 触发 loader 预取数据
      navigate(to, { preventScrollReset: true });
    });
  };

  return (
    <Link to={to} onMouseEnter={handleMouseEnter}
          style={{ opacity: isLoading ? 0.6 : 1 }}>
      {children}
    </Link>
  );
}

✅ 六、工程化落地建议

场景 推荐方案 工具
首屏图片 <img loading="lazy"> + 渐进式 JPEG ✅ 原生支持
路由切换 React.lazy + Suspense + SWR 数据预取 React Router + SWR
模块联邦 Remote Remote 内部做懒加载 + Host 预加载关键 Remote Webpack Module Federation
重型功能
(报表/导出)
点击时 import() + 骨架屏 Vite code split
高概率下一步操作 hover 时动态插入 <link rel="prefetch"> 自研预加载器
🎯 核心原则:

← 返回首页