前端性能优化经验分享
关于性能优化
在开始之前,我们需要明白一个原则:性能优化的最终目的是提升用户体验。
简而言之就是让用户感觉这个网站很「快」,这里的「快」有两种,一种是「真的快」一种是「觉得快」
- 「真的快」:可以客观衡量的指标,像网页访问时间、交互响应时间、跳转页面时间
- 「觉得快」:用户主观感知的性能,通过视觉引导等手段转移用户对等待时间的关注 做好这两方面都能提升用户对网站的性能评价。
瓶颈分析
Network分析
通过对接口的请求时间及调用次数进行优化
Lighthouse分析
评分工具就不多介绍了,针对给出的优化点逐一进行优化
Performance分析
性能分析工具就比较复杂了,需要具体通过某一段时间内页面交互的运行情况,有明显卡顿现象,对右上角红色三角的长任务代码进行优化
chunk分析
通过分析打包体积来尽可能减少打包后体积
具体怎么优化呢?
路由懒加载
SPA 项目,一个路由对应一个页面,如果不做处理,项目打包后,会把所有页面打包成一个文件,当用户打开首页时,会一次性加载所有的资源,造成首页加载很慢,降低用户体验
打包前:
- app.js 初始体积:
1175 KB
将路由全部改成懒加载
重新打包后,首页资源拆分为 app.js 和 home.js,以及对应的 css 文件
- app.js:
244 KB
、 home.js:35KB
通过路由懒加载,该项目的首页资源压缩约 52%
路由懒加载的原理
懒加载前提的实现:ES6的动态地加载模块——import()
调用 import() 之处,被作为分离的模块起点,意思是,被请求的模块和它引用的所有子模块,会分离到一个单独的 chunk 中
组件懒加载
除了路由的懒加载外,组件的懒加载在很多场景下也有重要的作用
举个🌰:比如弹窗组件 A页面和b页面都引用了弹窗组件 此时项目打包后发现会重复引用弹窗组件
当用户打开 home 页时,会一次性加载该页面所有的资源,我们期望的是用户触发按钮后,再加载该弹框组件的资源
这种场景下,就很适合用懒加载的方式引入
弹框组件懒加载:
<script>
const dialogInfo = () =>
import(/* webpackChunkName: "dialogInfo" */ 'src/components/dialogInfo');
</script>
重新打包后,当用户点击按钮时,才会去加载 dialogInfo.js 和 dialogInfo.css
组件懒加载的使用场景
有时资源拆分的过细也不好,可能会造成浏览器 http 请求的增多
总结出三种适合组件懒加载的场景:
1)该页面的 JS 文件体积大,导致页面打开慢,可以通过组件懒加载进行资源拆分,利用浏览器并行下载资源,提升下载速度(比如首页)
2)该组件不是一进入页面就展示,需要一定条件下才触发(比如弹框组件)
3)该组件复用性高,很多页面都有引入,利用组件懒加载抽离出该组件,一方面可以很好利用缓存,同时也可以减少页面的 JS 文件大小(比如表格组件、图形组件等)
对于图片资源
图片压缩、每次使用在线服务手动压缩较为麻烦,可以直接在构建流程中加入压缩图片
// install
npm i image-webpack-loader -D
rules: [
{
test: /.(jpe?g|png|gif|svg)$/i,
type: 'asset/resource',
generator: {
filename: 'images/[name].[contenthash:8].[ext]',
},
use: [
{
loader: 'image-webpack-loader',
},
],
图片压缩前后对比图:
图片的动态裁剪
只需在图片的url地址上动态添加参数,就可以得到你所需要的尺寸大小,比如:
7xkv1q.com1.z0.glb.clouddn.com/grape.jpg?i…
图片瘦身前后对比:
经过动态裁剪后的图片,加载速度会有非常明显的提升
图片的懒加载
- 对于一些图片量比较大的首页,用户打开页面后,只需要呈现出在屏幕可视区域内的图片,当用户滑动页面时,再去加载出现在屏幕内的图片,以优化图片的加载效果
- 图片懒加载实现原理:由于浏览器会自动对页面中的 img 标签的 src 属性发送请求并下载图片,可以通过 html5 自定义属性 data-xxx 先暂存 src 的值,然后在图片出现在屏幕可视区域的时候,再将 data-xxx 的值重新赋值到 img 的 src 属性即可
- 通过IntersectionObserver API 或节流监听scroll事件 来对 img src属性进行赋值
<img src="" alt="" data-src="./images/1.jpg">
<img src="" alt="" data-src="./images/2.jpg">
现有一些较为流行的库 如:react-lazyload
JS 的6种加载方式
-
正常模式
<script src="index.js"></script>
这种情况下 JS 会阻塞 DOM 渲染,浏览器必须等待 index.js 加载和执行完成后才能去做其它事情
-
async 模式
<script async src="index.js"></script>
async 模式下,它的加载是异步的,JS 不会阻塞 DOM 的渲染, 加载是无顺序的,当它加载结束,JS 会立即执行
使用场景:若该 JS 资源与 DOM 元素没有依赖关系,也不会产生其他资源所需要的数据时,可以使用async 模式,比如埋点统计、广告等
-
defer 模式
<script defer src="index.js"></script>
defer 模式下,JS 的加载也是异步的也不会阻塞 DOM 的渲染,defer 资源会在 DOMContentLoaded
执行之前,并且 defer 是有顺序的加载
如果有多个设置了 defer 的 script 标签存在,则会按照引入的前后顺序执行,即便是后面的 script 资源先返回
所以 defer 可以用来控制 JS 文件的执行顺序,比如 antd.js 和 react.js,因为 antd.js 依赖于 react.js,所以必须先引入 react.js,再引入 antd.js
<script defer src="react.js"></script>
<script defer src="antd.js"></script>
defer 使用场景:一般情况下都可以使用 defer,特别是需要控制资源加载顺序时
-
module 模式
<script type="module">
import { a } from './a.js'
</script>
在主流的现代浏览器中,script 标签的属性可以加上 type="module"
,浏览器会对其内部的 import 引用发起 HTTP 请求,获取模块内容。这时 script 的行为会像是 defer 一样,在后台下载,并且等待 DOM 解析
-
preload 预加载
<link rel="preload" as="script" href="index.js">
表示用户十分有可能需要在当前浏览中加载目标资源,所以浏览器必须预先获取和缓存对应资源。
preload 加载的资源是在浏览器渲染机制之前进行处理的,并且不会阻塞 onload 事件;
preload 加载的 JS 脚本其加载和执行的过程是分离的,即 preload 会预加载相应的脚本代码,待到需要时自行调用
-
Prefetch预请求
<link rel="prefetch" as="script" href="index.js">
prefetch 是利用浏览器的空闲时间,加载页面将来可能用到的资源的一种机制;通常可以用于加载其他页面(非首页)所需要的资源,以便加快后续页面的打开速度
Web Worker 优化长任务
在HTML5的新规范中,实现了 Web Worker 来引入 js 的 “多线程” 技术, 可以让我们在页面主运行的 js 线程中,加载运行另外单独的一个或者多个 js 线程
由于浏览器 GUI 渲染线程与 JS 引擎线程是互斥的关系,当页面中有很多长任务时,会造成页面 UI 阻塞,出现界面卡顿、掉帧等情况
计算时长 超过多长时间 适合用Web Worker
原则上,运算时间超过50ms会造成页面卡顿,属于Long task,这种情况就可以考虑使用Web Worker
但还要先考虑通信时长
的问题
假如一个运算执行时长为100ms, 但是通信时长为300ms, 用了Web Worker可能会更慢
一句话: Web Worker专门处理复杂计算的,从此让前端拥有后端的计算能力
虚拟滚动
虚拟滚动基本原理:
计算出 totalHeight 列表总高度,并在触发时滚动事件时根据 scrollTop 值不断更新 startIndex 以及 endIndex ,以此从列表数据 listData 中截取对应元素
虚拟滚动的插件有很多,比如 react-window、react-virtualized-auto-sizer、react-tiny-virtual-list、react-virtualized 等
还有很多其他的优化手段 骨架屏 http2/3 gzip压缩 托管oss+cdn加速等
转载自:https://juejin.cn/post/7274430147126935567