vue 项目中的性能分析及性能优化

本文先从 Vue 项目在整体上的执行流程谈起,然后详细介绍性能优化的两个重要方面:网络请求优化和代码效率优化。不过要明确一点,那就是在性能优化之外,用户体验才是性能优化的目的

用户输入 URL 到页面显示的过程

简单来说,就是用户在输入 URL 并且敲击回车之后,浏览器会去查询当前域名对应的 IP 地址。对于 IP 地址来说,它就相当于域名后面的服务器在互联网世界的门牌号。然后,浏览器会向服务器发起一个网络请求,服务器会把浏览器请求的 HTML 代码返回给浏览器。之后,浏览器会解析这段 HTML 代码,并且加载 HTML 代码中需要加载的 CSS 和 JavaScript,然后开始执行 JavaScript 代码。进入到项目的代码逻辑中,可以看到 Vue 中通过 vue-router 计算出当前路由匹配的组件,并且把这些组件显示到页面中,这样页面就完全显示出来了。而性能优化的主要目的,就是让页面显示过程的时间再缩短一些。

网络请求优化

对于前端来说,可以优化的点,首先就是在首页的标签中,使用标签去通知浏览器对页面中出现的其他域名去做 DNS 的预解析,比如页面中的图片通常都是放置在独立的 CDN 域名下,这样页面加载首页的时候就能预先解析域名并把结果缓存起来 。以淘宝网的首页为例进行分析,可以在淘宝的首页源码中看到下图所示的一列 dns-prefetch 标签,这样首页再出现 img.alicdn.com 这个域名请求的时候,浏览器就可以从缓存中直接获取对应的 IP 地址。

项目在整体流程中,会通过 HTTP 请求加载很多的 CSS、JavaScript,以及图片等静态资源。为了让这些文件在网络加载中更快,可以从后面这几方面入手进行优化。

首先,浏览器在获取网络文件时,需要通过 HTTP 请求,HTTP 协议底层的 TCP 协议每次创建链接的时候,都需要三次握手,而三次握手会造成额外的网络损耗。如果浏览器需要获取的文件较多,那就会因为三次握手次数过多,而带来过多网络损耗的问题。

所以,首先需要的是让文件尽可能地少,这就诞生出一些常见的优化策略,比如先给文件打包,之后再上线;使用 CSS 雪碧图来进行图片打包等等。文件打包这条策略在 HTTP2 全面普及之前还是有效的,但是在 HTTP2 普及之后,多路复用可以优化三次握手带来的网络损耗。

其次,除了让文件尽可能少,还可以想办法让这些文件尽可能地小一些,因为如果能减少文件的体积,那文件的加载速度自然也就会变快。这一环节也诞生出一些性能优化策略,比如 CSS 和 JavaScript 代码会在上线之前进行压缩;在图片格式的选择上,对于大部分图片来说,需要使用 JPG 格式,精细度要求高的图片才使用 PNG 格式;优先使用 WebP 等等。也就是说,尽可能在同等像素下,选择体积更小的图片格式。

在性能优化中,懒加载的方式也被广泛使用。图片懒加载的意思是:可以动态计算图片的位置,只需要正常加载首屏出现的图片,其他暂时没出现的图片只显示一个占位符,等到页面滚动到对应图片位置的时候,再去加载完整图片。

除了图片,项目中也会做路由懒加载,现在项目打包后,所有路由的代码都在首页一起加载。但也可以把不常用的路由单独打包,在用户访问到这个路由的时候再去加载代码。下面的代码中,vue-router 也提供了懒加载的使用方式,只有用户访问了 /course/:id 这个页面后,对应页面的代码才会加载执行。

css 复制代码
 {
    path: '/course/:id',
    component: () => import('../pages/courseInfo'),
  },

在文件大小的问题上,Lighthouse 已经给了比较详细的优化方法,比如控制图片大小、减少冗余代码等等,可以在项目打包的时候,使用可视化的插件来查看包大小的分布。

到项目根目录下,通过执行 npm install 操作来安装插件 rollup-plugin-visualizer。使用这个插件后,就可以获取到代码文件大小的报告了。之后,进入到 vite.config.js 这个文件中,新增下列代码,就可以在 Vite 中加载可视化分析插件。

scss 复制代码
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
  plugins: [vue(),vueJsx(), visualizer()],
})

然后,在项目的根目录下执行 npm run build 命令后,项目就把项目代码打包在根目录的 dist 目录下,并且根目录下多了一个文件 stat.html。用浏览器打开这个 stat 文件,就能看到下面的示意图。项目中的 ECharts 和 Element3 的体积远远大于项目代码的体积,这时候就需要用懒加载和按需加载的方式,去优化项目整体的体积。

那么这些文件如何才能高效复用呢?我们需要做的,就是尽可能高效地利用浏览器的缓存机制,在文件内容没有发生变化的时候,做到一次加载多次使用,项目中如果成功复用一个几百 KB 的文件,对于性能优化来说是一个巨大的提升。

浏览器的缓存机制有好几个 Headers 可以实现,Expires、Cache-control,last-modify、etag 这些缓存相关的 Header 可以让浏览器高效地利用文件缓存。我们需要做的是,只有当文件的内容修改了,才会重新加载文件。这也是为什么项目执行 npm run build 命令之后,静态资源都会带上一串 Hash 值,因为这样确保了只有文件内容发生变化的时候,文件名才会发生变化,其他情况都会复用缓存。

代码效率优化

在浏览器加载网络请求结束后,页面开始执行 JavaScript,因为 Vue 已经对项目做了很多内部的优化,所以在代码层面,我们需要做的优化并不多。很多 Vue 2 中的性能优化策略,在 Vue 3 时代已经不需要了,我们需要做的就是遵循 Vue 官方的最佳实践,其余的交给 Vue 自身来优化就可以了。

比如 computed 内置有缓存机制,比使用 watch 函数好一些;组件里也优先使用 template 去激活 Vue 内置的静态标记,也就是能够对代码执行效率进行优化;v-for 循环渲染一定要有 key,从而能够在虚拟 DOM 计算 Diff 的时候更高效复用标签等等。然后就是 JavaScript 本身的性能优化,或者说某些实现场景算法的选择了,这里需要具体问题具体分析,在通过性能监测工具发现代码运行的瓶颈后,依次对耗时过长的函数进行优化即可。

比如下面的代码,实现了一个斐波那契数列,也就是说,在实现的这个数列中,每一个数的值是前面两个数的值之和。可以使用简单的递归算法实现斐波那契数列后,在页面显示计算结果。

scss 复制代码
function fib(n){
  if(n<=1) return 1
  return fib(n-1)+fib(n-2)
}
let count = ref(fib(38))

上面的代码在功能上,虽然实现了斐波那契数列的要求,但是我们能够感觉到页面有些卡顿,所以可以来对页面的性能做一下检测。打开调试窗口中的 Performance 面板,使用录制功能后,便可得到下面的火焰图。通过这个火焰图,可以清晰地定位出这个项目中,整体而言耗时最长的 fib 函数,并且能看到这个函数被递归执行了无数次。到这里,不难意识到这段代码有性能问题。不过,定位到问题出现的地方之后,代码性能的优化就变得方向明确了。

下面的代码中,使用递推的方式优化了斐波那契数列的计算过程,页面也变得流畅起来,这样优化就算完成了。其实对于斐波那契数列的计算而言,得到最好性能的方式是使用数学公式 + 矩阵来计算。不过在项目瓶颈到来之前,采用下面的算法已经足够了,这也是性能优化另外一个重要原则,那就是不要过度优化。

scss 复制代码
function fib(n){
  let arr = [1,1]
  let i = 2
  while(i<=n){
    arr[i] = arr[i-1]+arr[i-2]
    i++
  }
  return arr[n]
}

用户体验优化

性能优化的主要目的,还是为了能让用户在浏览网页的时候感觉更舒服,所以有些场景不能只考虑单纯的性能指标,还要结合用户的交互体验进行设计,必要的时候,可以损失一些性能去换取交互体验的提升。

比如用户加载大量图片的同时,如果本身图片清晰度较高,那直接加载的话,页面会有很多图一直是白框。所以可以预先解析出图片的一个模糊版本,加载图片的时候,先加载这个模糊的图作为占位符,然后再去加载清晰的版本。虽然额外加载了图片文件,但是用户在体验上得到了提升。

类似的场景还有很多,比如用户上传文件的时候,如果文件过大,那么上传可能就会很耗时。而且一旦上传的过程中发生了网络中断,那上传就前功尽弃了。为了提高用户的体验,可以选择断点续传,也就是把文件切分成小块后,挨个上传。这样即使中间上传中断,但下次再上传时,只上传缺失的那些部分就可以了。可以看到,断点上传虽然在性能上,会造成网络请求变多的问题,但也极大地提高了用户上传的体验。

还有很多组件库也会提供骨架图的组件,能够在页面还没有解析完成之前,先渲染一个页面的骨架和 loading 的状态,这样用户在页面加载的等待期就不至于一直白屏

性能监测报告

解释一下 FCP、TTI 和 LCP 这几个关键指标的含义。首先是 First Contentful Paint,通常简写为 FCP,它表示的是页面上呈现第一个 DOM 元素的时间。在此之前,页面都是白屏的状态;然后是 Time to interactive,通常简写为 TTI,也就是页面可以开始交互的时间;还有和用户体验相关的 Largest Contentful Paint,通常简写为 LCP,这是页面视口上最大的图片或者文本块渲染的时间,在这个时间,用户能看到渲染基本完成后的首页,这也是用户体验里非常重要的一个指标。

可以通过代码中的 performance 对象去动态获取性能指标数据,并且统一发送给后端,实现网页性能的监控。性能监控也是大型项目必备的监控系统之一,可以获取到用户电脑上项目运行的状态。下图展示了 performance 中所有的性能指标,可以通过这些指标计算出需要统计的性能结果。

javascript 复制代码
const timing = window.performance && window.performance.timing
const navigation = window.performance && window.performance.navigation

// DNS 解析:
const dns = timing.domainLookupEnd - timing.domainLookupStart

// 总体网络交互耗时:
const network = timing.responseEnd - timing.navigationStart

// 渲染处理:
const processing = (timing.domComplete || timing.domLoading) - timing.domLoading

// 可交互:
const active = timing.domInteractive - timing.navigationStart

在上面的代码中,通过 Performance API 获取了 DNS 解析、网络、渲染和可交互的时间消耗。有了这些指标后,就可以随时对用户端的性能进行检测,做到提前发现问题,提高项目的稳定性。

相关推荐
Erishen6 分钟前
🚀 重新定义前端组件安装体验:shadcn + Bun 的极致开发效率
前端
冬奇Lab6 分钟前
Vercel部署全攻略:从GitHub到上线,10分钟让你的前端项目免费拥有自己的域名
前端·后端·node.js
牛马1119 分钟前
Flutter Web性能优化标签解析
前端·flutter·性能优化
Bigger9 分钟前
Tauri (25)——消除搜索列表默认选中的 UI 闪动
前端·react.js·weui
李少兄21 分钟前
简单讲讲 SVG:前端开发中的矢量图形
前端·svg
前端小万22 分钟前
告别 CJS 库加载兼容坑
前端·前端工程化
恋猫de小郭22 分钟前
Flutter 3.38.1 之后,因为某些框架低级错误导致提交 Store 被拒
android·前端·flutter
JarvanMo26 分钟前
Flutter 需要 Hooks 吗?
前端
光影少年36 分钟前
前端如何虚拟列表优化?
前端·react native·react.js
Moment38 分钟前
一杯茶时间带你基于 Yjs 和 reactflow 构建协同流程图编辑器 😍😍😍
前端·后端·面试