🚀 懒加载与预加载:前端性能优化的两大核心策略
欢迎来到我的博客文章!所有文章都是满满的前端干货,文章简明扼要。
一、核心概念对比
| 策略 | 目标 | 时机 | 适用资源 | 风险 |
懒加载 (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( '@/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)
const routes = [{
path: '/profile',
element: (
<Suspense fallback={<Spinner />}>
<Profile />
</Suspense>
),
loader: () => import('./routes/profile.loader'),
}];
const routes = [{
path: '/settings',
component: () => import('@/views/Settings.vue')
}];
🚀 四、预加载(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 动态预加载(用户行为预测)
const prefetchCache = new Set<string>();
function prefetchRoute(path: string) {
if (prefetchCache.has(path)) return;
const link = document.createElement('link');
link.rel = 'prefetch';
link.as = 'script';
link.href = `/assets/${path}.js`;
document.head.appendChild(link);
prefetchCache.add(path);
}
navItems.forEach(item => {
item.addEventListener('mouseenter', () => {
prefetchRoute(item.dataset.route!);
});
});
🔧 进阶:结合 SWR 实现「数据 + 代码」并行预加载
button.onmouseenter = async () => {
import('@/pages/Dashboard').catch(() => {});
await swrCache.revalidate('/api/dashboard-data');
};
3. 路由级预加载(React Router v6.4+)
function NavItem({ to, children }: { to: string; children: ReactNode }) {
const { isLoading } = useNavigation();
const handleMouseEnter = () => {
startTransition(() => {
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"> | 自研预加载器 |
🎯 核心原则: - 懒加载优先:所有非首屏资源都应该懒加载
- 智能预加载:基于用户行为预测,预加载高概率资源
- 渐进增强:先保证基础功能,再优化加载体验
- 监控反馈:通过性能监控验证优化效果
- 用户体验:loading 状态和骨架屏必不可少