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 解析、网络、渲染和可交互的时间消耗。有了这些指标后,就可以随时对用户端的性能进行检测,做到提前发现问题,提高项目的稳定性。

相关推荐
susu1083018911几秒前
前端css样式覆盖
前端·css
学习路上的小刘2 分钟前
vue h5 蓝牙连接 webBluetooth API
前端·javascript·vue.js
&白帝&2 分钟前
vue3常用的组件间通信
前端·javascript·vue.js
小白小白从不日白13 分钟前
react 组件通讯
前端·react.js
Redstone Monstrosity30 分钟前
字节二面
前端·面试
东方翱翔37 分钟前
CSS的三种基本选择器
前端·css
Fan_web1 小时前
JavaScript高级——闭包应用-自定义js模块
开发语言·前端·javascript·css·html
yanglamei19621 小时前
基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
前端·数据库·flask
千穹凌帝1 小时前
SpinalHDL之结构(二)
开发语言·前端·fpga开发
dot.Net安全矩阵1 小时前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net