gitbook/玩转Vue 3全家桶/docs/455487.md
2022-09-03 22:05:03 +08:00

180 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 18 | 实战痛点4Vue 3 项目中的性能优化
你好我是大圣欢迎进入课程的第18讲。
在上一讲中我们聊了项目中的权限设计相信你对Vue项目中如何设计以及如何实现权限都有了自己的心得。今天我们来聊一下Vue项目中的另外一个难点性能优化。性能优化是一个老生常谈的话题如果你是前端从业者那么无论是求职的简历还是晋升的PPT性能优化相关的内容都是不可或缺的。
那么在Vue项目中我们应该如何做性能优化呢下面我们会先从Vue项目在整体上的执行流程谈起然后详细介绍性能优化的两个重要方面**网络请求优化和代码效率优化**。不过,在性能优化之外,用户体验才是性能优化的目的,所以我也会简单谈一下用户体验方面的优化项。最后,我还会通过性能监测报告,为你指引出性能优化的方向。
## 用户输入URL到页面显示的过程
我们先来聊一个常见的面试题那就是用户从输入URL然后点击回车到页面完全显示出来这一过程中到底发生了什么
通过下图我们可以从前端的视角看到从输入URL到页面显示的大致过程
![图片](https://static001.geekbang.org/resource/image/95/5b/9550f050235a9bc0a91dc6e33f7e9e5b.jpg?wh=1920x923)
简单来说就是用户在输入URL并且敲击回车之后浏览器会去查询当前域名对应的IP地址。对于IP地址来说它就相当于域名后面的服务器在互联网世界的门牌号。然后浏览器会向服务器发起一个网络请求服务器会把浏览器请求的HTML代码返回给浏览器。
之后浏览器会解析这段HTML代码并且加载HTML代码中需要加载的CSS和JavaScript然后开始执行JavaScript代码。进入到项目的代码逻辑中可以看到Vue中通过vue-router计算出当前路由匹配的组件并且把这些组件显示到页面中这样我们的页面就完全显示出来了。而我们性能优化的主要目的就是让页面显示过程的时间再缩短一些。
## 性能优化
从用户输入URL到页面显示的过程这个问题包含着项目页面的执行流程。这个问题之所以重要是因为我们只有知道了在这个过程中每一步都发生了什么之后才能针对每一步去做网络请求的优化这也是性能优化必备的基础知识。
### 网络请求优化
对于前端来说可以优化的点首先就是在首页的标签中使用标签去通知浏览器对页面中出现的其他域名去做DNS的预解析比如页面中的图片通常都是放置在独立的CDN域名下这样页面加载首页的时候就能预先解析域名并把结果缓存起来 。
因为极客时间首页没做这个优化,所以我们以淘宝网的首页为例进行分析。你可以在[淘宝的首页源码](view-source:https://www.taobao.com)中看到下图所示的一列dns-prefetch标签这样首页再出现img.alicdn.com这个域名请求的时候浏览器就可以从缓存中直接获取对应的IP地址。
![图片](https://static001.geekbang.org/resource/image/23/e6/23163cccae366e93afbe0125c77838e6.png?wh=1330x524)
项目在整体流程中会通过HTTP请求加载很多的CSS、JavaScript以及图片等静态资源。为了让这些文件在网络加载中更快我们可以从后面这几方面入手进行优化。
首先浏览器在获取网络文件时需要通过HTTP请求HTTP协议底层的TCP协议每次创建链接的时候都需要三次握手而三次握手会造成额外的网络损耗。如果浏览器需要获取的文件较多那就会因为三次握手次数过多而带来过多网络损耗的问题。
所以首先我们需要的是让文件尽可能地少这就诞生出一些常见的优化策略比如先给文件打包之后再上线使用CSS雪碧图来进行图片打包等等。文件打包这条策略在HTTP2全面普及之前还是有效的但是在HTTP2普及之后多路复用可以优化三次握手带来的网络损耗。关于HTTP2的更多内容你可以去搜索相关文章自行学习。
其次,**除了让文件尽可能少,我们还可以想办法让这些文件尽可能地小一些**因为如果能减少文件的体积那文件的加载速度自然也就会变快。这一环节也诞生出一些性能优化策略比如CSS和JavaScript代码会在上线之前进行压缩在图片格式的选择上对于大部分图片来说需要使用JPG格式精细度要求高的图片才使用PNG格式优先使用WebP等等。也就是说尽可能在同等像素下选择体积更小的图片格式。
在性能优化中,懒加载的方式也被广泛使用。图片懒加载的意思是,我们可以动态计算图片的位置,只需要正常加载首屏出现的图片,其他暂时没出现的图片只显示一个占位符,等到页面滚动到对应图片位置的时候,再去加载完整图片。
除了图片项目中也会做路由懒加载现在项目打包后所有路由的代码都在首页一起加载。但是我们也可以把不常用的路由单独打包在用户访问到这个路由的时候再去加载代码。下面的代码中vue-router也提供了懒加载的使用方式只有用户访问了/course/:id这个页面后对应页面的代码才会加载执行。
```javascript
{
path: '/course/:id',
component: () => import('../pages/courseInfo'),
},
```
在文件大小的问题上Lighthouse已经给了我们比较详细的优化方法比如控制图片大小、减少冗余代码等等我们可以在项目打包的时候使用可视化的插件来查看包大小的分布。
我们来到项目根目录下通过执行npm install操作来安装插件rollup-plugin-visualizer。使用这个插件后我们就可以获取到代码文件大小的报告了。之后进入到vite.config.js这个文件中新增下列代码就可以在Vite中加载可视化分析插件。
```javascript
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [vue(),vueJsx(), visualizer()],
})
```
然后,我们在项目的根目录下执行 npm run build命令后项目就把项目代码打包在根目录的dist目录下并且根目录下多了一个文件stat.html。
我们用浏览器打开这个stat文件就能看到下面的示意图。项目中的ECharts和Element3的体积远远大于项目代码的体积这时候我们就需要用懒加载和按需加载的方式去优化项目整体的体积。
![图片](https://static001.geekbang.org/resource/image/de/89/de38f42fb62c74yy9eb964fd399f7d89.png?wh=1920x1109)
那么这些文件如何才能高效复用呢?**我们需要****做的,就是****尽可能高效****地****利用浏览器的缓存机制,在文件内容没有发生变化的时候,做到一次加载多次使用**项目中如果成功复用一个几百KB的文件对于性能优化来说是一个巨大的提升。
浏览器的缓存机制有好几个Headers可以实现Expires、Cache-controllast-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本身的性能优化或者说某些实现场景算法的选择了这里需要具体问题具体分析在通过性能监测工具发现代码运行的瓶颈后我们依次对耗时过长的函数进行优化即可。
我们来到src/App.vue文件中看下面的代码我们实现了一个斐波那契数列也就是说在我们实现的这个数列中每一个数的值是前面两个数的值之和。我们使用简单的递归算法实现斐波那契数列后在页面显示计算结果。
```javascript
function fib(n){
if(n<=1) return 1
return fib(n-1)+fib(n-2)
}
let count = ref(fib(38))
```
上面的代码在功能上,虽然实现了斐波那契数列的要求,但是我们能够感觉到页面有些卡顿,所以我们来对页面的性能做一下检测。
我们打开调试窗口中的Performance面板使用录制功能后便可得到下面的火焰图。通过这个火焰图我们可以清晰地定位出这个项目中整体而言耗时最长的fib函数并且我们能看到这个函数被递归执行了无数次。到这里我们不难意识到这段代码有性能问题。不过定位到问题出现的地方之后代码性能的优化就变得方向明确了。
![图片](https://static001.geekbang.org/resource/image/12/b9/12d7d30ea1f7bae6435fb6d5a21a80b9.png?wh=1920x1120)
下面的代码中,我们使用递推的方式优化了斐波那契数列的计算过程,页面也变得流畅起来,这样优化就算完成了。其实对于斐波那契数列的计算而言,得到最好性能的方式是使用数学公式+矩阵来计算。不过在项目瓶颈到来之前,我们采用下面的算法已经足够了,**这也是性能优化另外一个重要原则,那就是不要过度优化****。**
```javascript
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的状态这样用户在页面加载的等待期就不至于一直白屏下图所示就是antd-vue组件库骨架图渲染的结果。
![图片](https://static001.geekbang.org/resource/image/26/dc/26b97a7c7ba894d18ac6311a7fd966dc.gif?wh=872x472)
## 性能监测报告
在[第12讲](https://time.geekbang.org/column/article/442479)学习Vue Devtools的时候我们已经使用Chrome的性能监测工具Lighthouse对极客时间的官网做了一次性能的评估我们可以在这里看到[评测报告](https://pandafe.gitee.io/clock/time.geekbang.org.html)。并且我们也对如何在调试窗口的Performance页面中进行性能监控给出了演示。为了方便你理解我们在这里也解释一下FCP、TTI和LCP这几个关键指标的含义。
首先是First Contentful Paint通常简写为FCP它表示的是页面上呈现第一个DOM元素的时间。在此之前页面都是白屏的状态然后是Time to interactive通常简写为TTI也就是页面可以开始交互的时间还有和用户体验相关的Largest Contentful Paint通常简写为LCP这是页面视口上最大的图片或者文本块渲染的时间在这个时间用户能看到渲染基本完成后的首页这也是用户体验里非常重要的一个指标。
我们还可以通过代码中的performance对象去动态获取性能指标数据并且统一发送给后端实现网页性能的监控。性能监控也是大型项目必备的监控系统之一可以获取到用户电脑上项目运行的状态。
下图展示了performance中所有的性能指标我们可以通过这些指标计算出需要统计的性能结果。
![图片](https://static001.geekbang.org/resource/image/71/f1/71a25ac4634b288911f17beb97b429f1.png?wh=912x555)
```plain
let timing = window.performance && window.performance.timing
let navigation = window.performance && window.performance.navigation
DNS 解析:
let dns = timing.domainLookupEnd - timing.domainLookupStart
总体网络交互耗时:
let network = timing.responseEnd - timing.navigationStart
渲染处理:
let processing = (timing.domComplete || timing.domLoading) - timing.domLoading
可交互:
let active = timing.domInteractive - timing.navigationStart
```
在上面的代码中我们通过Performance API获取了DNS解析、网络、渲染和可交互的时间消耗。有了这些指标后我们可以随时对用户端的性能进行检测做到提前发现问题提高项目的稳定性。
## 总结
今天的主要内容就聊完啦,我们来复习一下今天学到的内容吧。
首先我们了解了用户从输入URL到页面显示的这一过程发生了什么这里面的每个流程都有值得优化的地方比如网络请求、页面渲染等。在对这些流程优化后网页运行时整体的性能都会得到提升。
之后在网络请求优化这一部分我们首先谈到对于DNS我们可以通过dns-prefetch预先获取这对性能优化来说会减少页面中其他域名请求的DNS解析时间因为TCP协议每次链接时都需要三次握手而这会带来额外的网络消耗的问题为了解决这一问题我们的优化策略是让文件尽可能少一些并且也小一些。
比如我们可以通过文件打包的形式减少HTTP请求数量这样对于文件的大小来说可以减小文件体积。我们也可以压缩代码以及选择更合适的图片格式这些都可以让我们加载更小的文件。图片的懒加载和路由的懒加载可以让首页加载更少的文件从而实现页面整体性能的优化。
在讲完网络请求优化后我们又研究了代码效率优化这个问题其实代码层面要做的优化并不多主要还是遵守Vue 3最佳实践。我们还以斐波那契数列的计算为例通过在Performance面板中进行性能监控明确了代码优化的方向。在通过递归的方式优化斐波那契数列之后我们能明白这样一点**性能优化的一个重要原则,是不要****过度****优化**。
之后,在用户体验优化这一部分,我们的关注点是在交互体验的优化上。有些场景我们可以损失部分性能去换取体验的提升,比如通过骨架图,我们可以在页面加载之前,通过对图片预先加载出模糊版本,可以让用户获得更好的体验。
最后在性能监测报告这一部分我讲到选择合适的工具可以帮助我们实时地监测项目的性能。我们通过Lighthouse性能报告和Performace监测工具可以精确地定位到项目瓶颈所在有针对地去进行性能优化。
## 思考题
最后,给你留一个思考题:通过今天的学习,想一想你负责的项目都有哪些可以优化的地方呢?
欢迎在留言区留言分享你的想法,也欢迎你把这一讲的内容推荐给你的朋友、同事们,我们下一讲见。