前言
在前端项目中,性能优化是一个完整的分析过程。通常需要先通过浏览器开发者工具定位问题,再根据资源类型选择合适的优化方案,最后通过构建结果和性能工具验证优化效果。
本文以一个 Vue3 电商项目为例,记录常见的前端性能优化思路,包括:
- 使用 Network 分析资源加载情况
- 使用路由懒加载减少首屏 JS 体积
- 使用图片懒加载减少非首屏图片请求
- 使用 WebP 优化本地大图体积
- 使用 rollup-plugin-visualizer 分析打包产物
- 使用 Lighthouse 验证优化效果
项目技术栈:
Vue3 Vite Vue Router Pinia Element Plus Axios
一.使用 Network 分析页面资源
Chrome DevTools 的 Network 面板是前端性能分析中最常用的工具之一。
Network 面板中可以重点关注以下信息:
- 请求数量
- 资源体积
- JS/CSS/图片大小
- DOMContentLoaded时间:此时HTML解析完成,DOM树构建完成
- Load时间:页面主要资源加载完成
- Finish时间:Network面板中所有请求结束
- Initiator:请求来源
对于电商项目来说,图片资源 通常占比较大。如果页面主体已经加载完成,但Finish 时间仍然很长,往往说明后续还有大量图片资源在继续加载。
在前端项目中,电商网站常常有很多的图片加载,导致首屏加载速度很慢,为此要进行优化,提高用户体验。
优化前的性能(使用谷歌浏览器无痕模式,进入网站首页的加载情况):
项目打包后体积:

JS包比较大,可以用路由懒加载降低首屏资源体积
通过network可以看到加载数据的时长

- 101个请求:页面一共请求了 101 个资源,包括 HTML、JS、CSS、图片、接口等。
- 已传输 37.1 MB:浏览器实际从网络下载了 37.1MB,偏大。
- DOMContentLoaded 3.68 秒:HTML 解析完成,JS 基本开始接管页面。
- 加载时间4.06 秒:页面 load 事件完成时间。
- 完成用时24.10 秒 :Network 里所有请求基本结束的时间。
二.路由懒加载
在 Vue 单页应用中,如果所有页面组件都通过静态 import 导入,打包时可能会被合并到较大的主 JS 文件中。
用户进入首页时,即使没有访问登录页、购物车页、结算页,也会提前下载这些页面的代码。
我们可以将路由组件改为动态 import:
javascript
const Home = () => import('@/views/Home/index.vue')
const Detail = () => import('@/views/Detail/index.vue')
使用动态导入后,Vite 会自动进行代码分割 ,将不同页面拆分为独立 chunk。用户访问某个路由时,才加载对应页面的代码。
javascript
children:[
{
path:'',//默认子路由(默认二级路由),当访问/时该路由也会被渲染展示,与/路由同时展示
component:Home
},
{
path:'detail/:id',//动态路由参数
component:Detail
},
{
path:'cartlist',
component:CartList
}
]
优化前主 JS 为 409.35KB,gzip 后 147.68KB;优化后主 JS 为 125.18KB,gzip 后 49.11KB,首屏需要下载的 JS 明显减少。
主 JS 包从 409.35 kB 降到 125.18 kB ,gzip 后从 147.68 kB 降到 49.11 kB。
npm run build:

npm run preview:(筛选js)

二.图片懒加载
电商项目中商品图片数量较多,如果所有图片都在页面初始化时加载,会增加首屏网络压力。
图片懒加载的核心思路是:
- 图片没有进入视口时,不加载真实图片
- 图片进入视口后,再设置真实src
项目里使用自定义指令,用vueuse里封装好的 useIntersectionObserver监听器来监听图片是否进入视图,如果图片进入视图,才把图片真正的地址赋给src。
javascript
//定义懒加载插件
import { useIntersectionObserver } from '@vueuse/core'
import loadingImg from '@/assets/images/loading.gif'
export const lazyPlugin = {
install(app){
//懒加载指令逻辑
app.directive('img-lazy',{
mounted(el,binding){
el.src = loadingImg
//el:指令绑定的元素 img
//binding:binding.value 指令等于号后面绑定的表达式的值 图片url
const { stop } = useIntersectionObserver(
el,
([entry]) => {
if(entry.isIntersecting){
//图片进入视口区域
el.src = binding.value
stop()
}
}
)
}
})
}
}
在模板中使用:
javascript
<img v-img-lazy="item.picture" alt="" />
优化前,页面初始化时图片请求数量多,部分非首屏商品图片也会提前请求。优化后,将首页新鲜好物、分类浮层、商品列表等非首屏图片统一改为自定义懒加载指令。
刷新后不滚动时,图片请求为 43 个,传输约 1.67MB;滚动页面后,图片请求增加到 54 个,传输增加到 3.76MB,说明非首屏图片是在进入视口后才开始加载。
图片懒加载后network数据。

三.图片压缩
图片懒加载解决的是"什么时候加载",但不能减少图片本身体积。
接口返回的图片资源前端不适合直接改,但是一些本地的图片我们可以进行压缩成其他格式减小体积。 比如登录页背景图 png 改成 webp 格式 ,通常 WebP 会比 PNG/JPG 小很多,尤其适合网页背景图。可以使用在线工具进行格式转换,比如Squoosh。网址:https://squoosh.app/
将png图片转换为webp图片使用。
四.打包体积分析
npm run build 可以看到构建产物大小,但无法直观看到每个 chunk 内部由哪些模块组成。
此时可以使用 rollup-plugin-visualizer 生成打包分析报告,用于观察 bundle 内部组成。
如何使用rollup-plugin-visualizer?
1.npm install rollup-plugin-visualizer -D
2.修改 vite.config.js(在 vite.config.js 中按需开启)
javascript
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig(({ mode }) => ({
plugins: [
vue(),
mode === 'analyze' && visualizer({
filename: 'dist/stats.html',
open: true,
gzipSize: true,
brotliSize: true
})
].filter(Boolean)
}))
3. 在 package.json 中添加分析命令
javascript
{
"scripts": {
"build:analyze": "vite build --mode analyze"
}
}
4.运行分析 npm run build:analyze
5.成功后会生成 dist/stats.html
从分析结果可以看到,项目主要由 Vue 核心、vue-router、Pinia、axios、@vueuse/core、Element Plus 以及业务页面代码组成。路由懒加载后,Home、Detail、Login、CartList、Checkout、Pay 等页面已经被拆分为独立 chunk,说明代码分割生效。
同时,Element Plus 相关组件如 button、input、checkbox、popover、dialog 等也以按需方式进入构建产物,没有把整个组件库一次性打进主包。

六.Lighthouse 性能验证
优化完成后,可以使用Lighthouse 对页面进行测试。

指标含义:
- FCP:首次内容绘制,表示用户第一次看到页面内容的时间
- LCP:最大内容绘制,通常与首屏大图或主要内容有关
- TBT:总阻塞时间,反映 JS 是否长时间阻塞主线程
- CLS:布局偏移,反映页面加载过程中是否发生明显抖动
- Speed Index:页面视觉加载速度
从结果来看,页面首屏展示速度较快,JS 没有明显阻塞,布局也较稳定。
Lighthouse 仍然提示了一些后续优化方向,例如: