前端性能优化实战:从加载速度到渲染效率的全面提速方案
为什么我又在折腾性能优化?
最近接手一个老项目,首页加载慢得像卡碟,用户反馈“点进去以为崩了”。打开 DevTools 一看,Lighthouse 分数才 40 多,首屏时间 5 秒+。说实话,这种问题我见多了——不是没做优化,而是用错了方案,或者“优化”反而成了负担。
这次我重点对比了三种主流的前端性能优化手段:代码分割(Code Splitting)、懒加载(Lazy Loading)和预加载(Prefetching)。很多人一上来就说“用 Webpack 分割”,但实际场景复杂得多。下面我就结合真实项目经验,说说我踩过的坑、试过的招,以及最后怎么选的。
代码分割:真香,但别乱切
我一开始也迷信“越大越要拆”,结果把 vendor.js 拆成七八个 chunk,首屏反而更慢了——HTTP 请求太多,浏览器并发限制直接拖垮体验。
后来我改用路由级 + 组件级组合拆分。比如 React 项目里,用 React.lazy + Suspense 配合路由:
const HomePage = React.lazy(() => import('./pages/Home'));
const ProfilePage = React.lazy(() => import('./pages/Profile'));
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/profile" element={<ProfilePage />} />
</Routes>
</Suspense>
);
}
这招对 SPA 特别有效。但注意:别把高频共用组件(比如 Header、Footer)也 lazy 掉,否则每次跳转都闪一下加载态,用户体验更差。
另外,Webpack 的 magic comments 很关键:
const Chart = React.lazy(() => import(
/* webpackChunkName: "chart-lib" */
'./components/Chart'
));
这样能避免自动生成一堆 hash 命名的 chunk,方便缓存管理。我之前没加这个,每次构建 chunk 名全变,CDN 缓存全废,气得半夜改配置。
懒加载图片:简单粗暴,但细节坑多
图片懒加载几乎是标配了,但很多人直接上第三方库(比如 lozad.js),结果发现兼容性问题一堆。现在原生 loading="lazy" 已经覆盖主流浏览器,我基本不用库了。
<img src="placeholder.jpg"
data-src="https://jztheme.com/images/hero.jpg"
loading="lazy"
alt="示例图">
等等,上面写法其实不对!loading="lazy" 要直接作用于真实 src,否则无效:
<img src="https://jztheme.com/images/hero.jpg" loading="lazy" alt="示例图">
我之前被 placeholder 方案绕晕了,折腾半天才发现原生方案更稳。不过注意:首屏关键图千万别 lazy,否则 LCP 直接崩掉。我的做法是:首屏图正常加载,滚动区域以下的图才 lazy。
还有个坑:懒加载组件(比如评论区)如果依赖 Intersection Observer,记得 polyfill。Safari 12.1 以下不支持,老设备用户会看不到内容。
预加载:用对了起飞,用错了浪费带宽
预加载是我最近才敢大胆用的技术。以前总觉得“提前加载=浪费流量”,但实测发现,对关键资源非常有效。
比如用户大概率会点“下一步”的页面,我可以提前 prefetch:
<link rel="prefetch" href="/next-step.js" as="script">
或者用 JS 动态触发:
// 用户停留超过 3 秒,预加载下一个路由
setTimeout(() => {
const link = document.createElement('link');
link.rel = 'prefetch';
link.as = 'script';
link.href = '/checkout.js';
document.head.appendChild(link);
}, 3000);
但这里有个大坑:prefetch 是低优先级的,不会阻塞当前页面,但如果你用 preload(高优先级),一不小心就把带宽占满,导致主资源加载变慢。我之前把字体文件 preload 了,结果首屏 JS 被卡住,Lighthouse 报警。
所以我的原则是:只对高确定性、高价值的资源做预加载。比如电商详情页的“立即购买”按钮对应逻辑,或者登录后必进的仪表盘。
我的选型逻辑:没有银弹,只有权衡
回到开头那个慢项目,我最终方案是:
- 路由级代码分割:所有非首页路由 lazy load
- 图片原生 lazy:非首屏图全部
loading="lazy" - 关键 JS 预加载:用户行为预测后,动态 prefetch 下一步脚本
改完后 Lighthouse 分数从 42 提到 89,首屏时间压到 1.8 秒。当然,还有两个小问题没解决:低端安卓机上 lazy 图偶尔白屏(Intersection Observer 精度问题),以及 Safari 对 prefetch 支持弱。但整体收益远大于成本,先上线再说。
说到底,性能优化不是堆技术,而是根据业务场景做取舍。如果你的用户都在 4G 环境,别搞太多 prefetch;如果你的 SPA 路由简单,可能连代码分割都不需要。
我比较喜欢“最小必要优化”原则:先测真实数据(别信理论),再针对性下手。有时候删掉一个没用的第三方库,比折腾 Webpack 配置效果还好。
结尾碎碎念
以上是我最近一次性能优化的完整思路和踩坑记录。代码分割、懒加载、预加载各有适用场景,没有绝对优劣。关键是别为了“用了新技术”而优化,而是为了解决真实用户问题。
如果你有更好的实践,比如如何优雅处理 lazy 组件的骨架屏,或者动态 prefetch 的精准触发策略,欢迎评论区交流。这个主题水太深,我还在摸索,后续可能会写一篇关于“如何用 Performance API 做自动化性能监控”的实战。

暂无评论