4. 前端性能优化(三):懒加载与预加载

各位同学,今天我们来聊聊前端性能优化里非常实用的一招——懒加载与预加载。说实话,这两个概念听起来有点像「双胞胎」,但它们的应用场景和时机完全不同。我在项目中踩过不少坑,今天一并分享给你们。

4.1 图片懒加载:别让用户为看不见的东西买单

先问大家一个问题:一个页面有 100 张图片,用户只看了前 3 张,剩下的 97 张图片要不要加载?

答案显然是否定的。但很多新手项目就是这么干的——所有图片一股脑全请求。结果呢?首屏加载慢得像蜗牛,用户早跑了。

图片懒加载的核心思想:只有当图片进入视口(viewport)时,才去加载它。说白了,就是「按需加载」。

4.1.1 传统实现方式:监听滚动事件

我记得早期做电商项目时,用的就是监听 scroll 事件 + 计算元素位置。代码大概长这样:

function lazyLoad() {
  const images = document.querySelectorAll('img[data-src]');
  images.forEach(img => {
    const rect = img.getBoundingClientRect();
    if (rect.top < window.innerHeight) {
      img.src = img.dataset.src;
      img.removeAttribute('data-src');
    }
  });
}

window.addEventListener('scroll', throttle(lazyLoad, 200));

嗯,这里要注意:一定要加节流(throttle)。不然滚动一次触发几十次计算,性能反而更差。我当年就吃过这个亏,页面卡得不行,后来加了 200ms 的节流才解决。

4.1.2 现代方案:Intersection Observer API

现在浏览器提供了更优雅的 API——IntersectionObserver。它就像一个「哨兵」,自动帮我们监测元素是否进入视口。

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      observer.unobserve(img);
    }
  });
});

document.querySelectorAll('img[data-src]').forEach(img => {
  observer.observe(img);
});
我的建议:能用 IntersectionObserver 就别用 scroll 监听。它由浏览器底层实现,性能好得多。而且代码更简洁,维护起来也省心。

4.1.3 占位与过渡

图片懒加载还有一个容易被忽略的点——占位。如果图片还没加载出来,页面布局会「跳动」,用户体验很差。

我个人习惯的做法:

  • 先用一个低分辨率的模糊图或纯色背景占位
  • 等原图加载完成后,做一个淡入过渡
img {
  opacity: 0;
  transition: opacity 0.3s ease;
}
img.loaded {
  opacity: 1;
}

你想想看,用户看到图片从模糊变清晰,是不是比突然「蹦」出来舒服多了?

4.2 路由懒加载:按需加载,别一次全塞给用户

单页应用(SPA)有个通病:所有页面代码打包成一个巨大的 JS 文件。用户访问首页,却把「个人中心」「设置页」「帮助页」的代码全下载了。这合理吗?

当然不合理。路由懒加载就是解决这个问题的——访问哪个路由,才加载哪个路由的代码

4.2.1 Vue 中的实现

在 Vue 项目中,我通常这样写:

const routes = [
  {
    path: '/home',
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/about',
    component: () => import('@/views/About.vue')
  }
];

这里用了动态 import(),Webpack 或 Vite 会自动把它拆分成独立的 chunk。用户访问 /home 时,只加载 Home 的代码。

我曾经踩过的坑:路由懒加载配合「预加载」时,要注意加载时机。如果用户快速切换路由,可能会触发多次加载。建议配合 webpackPrefetchwebpackPreload 来优化。

4.2.2 React 中的实现

React 里用 React.lazy + Suspense

const Home = React.lazy(() => import('./Home'));
const About = React.lazy(() => import('./About'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/home" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </Suspense>
  );
}

注意那个 fallback,它是加载过程中显示的占位内容。我建议不要只写「Loading...」,可以放一个骨架屏或者进度条,用户体验会好很多。

4.3 资源预加载:提前准备,让用户感觉「秒开」

懒加载是「晚点加载」,预加载正好相反——提前加载用户可能需要的资源

举个例子:用户鼠标 hover 到一个按钮上,大概率会点击。这时候提前加载按钮对应的页面,等用户真正点击时,页面已经准备好了。

4.3.1 使用 <link rel="preload">

这是浏览器原生支持的预加载方式:

<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="hero-image.webp" as="image">

preload 告诉浏览器:「这个资源很重要,尽快加载」。它适合加载首屏关键资源,比如字体、首屏大图、关键 CSS。

4.3.2 使用 <link rel="prefetch">

prefetch 则更温和一些:

<link rel="prefetch" href="next-page.js">

它告诉浏览器:「有空的话,帮我把这个资源加载了」。浏览器会在空闲时间下载,不影响当前页面性能。

核心区别:preload 是「必须的,尽快加载」;prefetch 是「可选的,有空再加载」。别搞混了,否则可能浪费带宽。

4.3.3 智能预加载策略

我在项目中用过一种「预测式预加载」:根据用户行为预测下一步操作。

  • 用户鼠标悬停在「下一页」按钮上 → 预加载下一页内容
  • 用户滚动到页面底部 → 预加载「相关文章」
  • 用户填写表单 → 预加载「提交成功」页面

代码实现也不复杂:

document.querySelector('.next-btn').addEventListener('mouseenter', () => {
  const link = document.createElement('link');
  link.rel = 'prefetch';
  link.href = '/next-page';
  document.head.appendChild(link);
});

嗯,这里要注意:别过度预加载。我曾经在一个项目里预加载了 10 多个页面,结果用户只看了 2 个,白白浪费了流量和带宽。预加载要「精准」,而不是「贪多」。

4.4 懒加载 vs 预加载:什么时候用哪个?

很多同学会问:这两个到底怎么选?我画了一张图,帮大家理清思路:

懒加载 vs 预加载 决策流程 用户需要这个资源吗? 是「现在需要」还是「将来需要」? 现在需要 立即加载 将来需要(可预测) 预加载(prefetch) 将来需要(不可预测) 懒加载 总结: • 用户马上要看 → 立即加载(preload) • 用户下一步可能看 → 预加载(prefetch) • 用户不确定什么时候看 → 懒加载(lazy load)

说白了,核心就一句话:「现在要用的,赶紧加载;将来要用的,提前准备;不确定的,等到了再说」

4.5 实战建议与避坑指南

我的实战经验:在一个在线学习系统里,我同时用了三种策略:
  • 课程封面图 → 懒加载(用户滚动到才加载)
  • 视频播放页 → 预加载(用户点击「开始学习」前就加载)
  • 首页关键 CSS → preload(保证首屏渲染速度)
效果很明显:首屏加载时间从 4.2s 降到了 1.8s。
避坑指南
  • 别对移动端用户过度预加载,流量很贵
  • 懒加载的图片要设置宽高,防止布局抖动
  • 路由懒加载的 chunk 命名要有规律,方便调试
  • 预加载不要和懒加载冲突——同一个资源别既预加载又懒加载

好了,这一章的内容就到这里。记住:性能优化不是「炫技」,而是让用户用得更舒服。懒加载和预加载就像「精打细算的管家」,知道什么时候该花钱,什么时候该省钱。你学会了吗?


公众号:蓝海资料掘金营,微信deep3321