vue项目性能优化

389 阅读8分钟

首屏加载速度优化

一、定位问题

  1. 使用静态资源分析工具,定位体积过大的资源【如:rollup-plugin-visualizer】
  2. 使用performance工具进行分析

二、性能优化实施

  1. 执行npm run build,根据分析工具查看哪些资源占用过大

image.png

整体优化流程

  1. 步骤一:将用到的第三方库,换成可以按需引入的版本 【适用于老项目,比如将老的xlxs升级,那么就可以用按需引入】
import{read,utils} from 'xlsx'
和全局引入相比,xlsx的按需引入大约可以缩短2s的加载速度

2. 路由懒加载

例子:使用vite搭建的项目

//Vite 默认支持 ES Modules 的动态导入语法,每个动态导入的模块会自动生成独立的 chunk
// 自动生成哈希命名的 chunk(如 src/views/AboutView.vue-123abc.js)
const AboutView = () => import('../views/AboutView.vue')

3. 组件异步加载 4. 图片资源压缩,使用雪碧图等

// 对于用到的图片,使用Tinypng压缩图片;
// icon可以使用字体图标
// 使用雪碧图,减少http请求压力
// 

5. 通过资源分析工具,将第三方资源包分离,如下

// 例如:vite搭建的项目中
// import externalGlobals from 'rollup-plugin-external-globals'
// key为依赖包名称,value为源码跑出来的全局变量
build: {
    rollupOptions: {
      external: ['vue', 'vue-router'],
      plugins: [
        externalGlobals({
          vue: 'Vue',
          'vue-router': 'VueRouter',
        }),
      ],
    },
  },

6. 代码相关优化

// 1. 减少内存泄漏
事件注册与事件移除
定时器移除
引用的对象及时清理
...

// 2. 对于数据量庞大的页面进行优化
数据懒加载
虚拟列表
滚动加载
防抖节流
...

vite性能优化

性能优化包含哪些

  1. 开发时的构建速度,执行npm run dev到看到结果的时间
webpack可以使用cache-loader
vite 是按需加载,所以我们不需要太care这方面

2. 页面性能指标:和我们怎么去写代码有关

  • 首屏渲染时长:fcp (页面中第一个元素的渲染时长)

    • 懒加载:需要代码实现
    • http优化:协商缓存和强缓存
      • 协商缓存:是否使用缓存要跟后端商量一下,当服务端给我们打上协商缓存的标记以后,客户端在下次刷新页面需要重新请求资源时会发送一个协商请求给到服务端,服务端如果说需要变化,则会响应具体的内容;如果服务端觉得没变化则会响应304。
      • 强缓存:服务端给响应头追加一些字段expires(资源截至失效时间),客户端会记住这些字段,在expires没有到达之前,无论你怎么刷新页面,浏览器都不会重新请求页面,而是从缓存里取
  • 页面中最大元素的一个时长:lcp

  1. js逻辑:
  • 注意副作用的清除(定时器,事件等),组件是会频繁的挂载和卸载:
    • 例如:我们在一个组件中有计时器(setTimeout),如果我们在卸载的时候不去清除这个计时器,下次再次挂载的时候计时器就等于开了两个线程
    • 我们在写法上需要注意的事项:requestAnimationFrame,requestIdleCallback
      • 浏览器的帧率:16.6ms去更新一次(执行js逻辑,以及重排重绘),假设我的js执行逻辑超过了16.6ms,那就没时间重排重绘了,就是掉帧了,会卡。
      • requestIdleCallback:传一个函数进去
    • 防抖节流,用lodash工具
    • 对作用域的一个控制
  1. css
  • 关注继承属性:能继承的就不要重复写
  • 尽量避免太过于深的css嵌套
  1. 生产的优化:vite(rollup),webpack
  • 优化体积:压缩,treeshaking,图片资源压缩,cdn加载,分包
vite构建优化相关
  1. 分包策略:
  • 前置知识:
    • 浏览器的缓存策略:
    • 静态资源的名字没有变化,那么他就不会重新请求,重新去拿
    • 文件的hash名:只要内容有一丁点的变化,hash字符串完全不一样
    • 我们的业务代码会经常变化
    • 但是依赖的第三方库不会变化,所以在这里是浪费了请求
    • 文件名不一样就会重新请求,一重新请求,就会把该文件中引用的第三方库也一并请求。(但其实第三方库是没变化的)
    • 分包就是把一些不会常规更新的文件, 进行单独打包处理
   // vite.config.js
  build: {
    rollupOptions: {
      input: {
      // 配置多文件也类似
        main: path.resolve(__dirname, './index.html'),
        product: path.resolve(__dirname, './product.html'),
      },
      output: {
        manualChunks: (id) => {
          if (id.includes('node_modules')) {
            // 如果当前字符串包含node_modules,那么就相当于是第三方库,也就是不会时常变动的文件
            return 'vendor'  // 文件名
          }
          console.log(id, 'ssss')
        },
      },
    },
  },

2. gzip压缩

  • 比如有的时候,我们的文件资源实在太大了(js占了2000kb,在http传输中就会有很大的传输压力),所以想到了一个办法。
    • 将所有的静态文件进行压缩,来达到减少体积的目的
    • 服务端可以读文件,将文件进行压缩
    • 将压缩后的文件,通过http传输,传给客户端
    • 客户端收到的是压缩包,浏览器进行解压缩的操作
  • 前置知识:什么是chunk (块)
    • 从入口文件到他的一系列依赖,最终打包成的js文件叫做块
    • 更确切的说,块最终会映射成js文件,但是块不是js文件
  • gz压缩
// vite.config.js
  plugins: [
    viteCompression(), // 这个就是gz压缩的插件
  ],
  • 当gzip压缩后,会生成gz文件
    • 做到这一步,就没前端什么事情了
    • 给到后端或者运维,商量一下(就是有几个文件是通过gzip压缩,待会请求index.html或者index.js的时候,给前端静态资源的时候,如果发现前端给你的是gzip的文件的时候,后端就别再压缩了,直接用就好)
    • 然后后端会读gzip文件(.gz),设置响应头(content-encoding:gzip),代表告诉浏览器,该文件是使用gzip压缩过的
    • 这时,浏览器收到响应结果,发现响应头里面有gzip对应字段,赶紧解压(自动解压),解压后就会得到原本的js文件。
    • 但是这样的话,浏览器需要承担一定的解压时间,所以如果体积不是很大的话,不要使用gzip
  1. 动态导入
  • webpack 与 vite构建原理的差别

    • webpack是将所有文件一次性打包完毕之后,然后才会开启开发服务器,当文件越来越多,webpack初期处理的东西就会越来越多,开启开发服务器就会很缓慢
    • vite是直接把index.html丢出去,index.html如果依赖了main.js, 再请求main.js,如果main.js中有其他的js,即vite是按需加载(没有被引用到的,vite就永远不会打包)
  • 动态导入与按需加载是异曲同工

    • 动态导入是es6的一个新特性,(就如同let const一样)
    // xx.js文件
    // 之前引入的写法:import './src/svgLoader'
    // 改之后:import('./src/svg/svgLoader').then(data=>{
                console.log(data,'ss')
            })
    // 这样写之后,就会将这个单独提出去一个文件,那么就有人会问了,虽然单独分出去了一个文件,但最后还是要加载在引入的文件内,文件其实并没有小
    // 但是动态导入通常会用于路由里面。
    
    • 路由中的动态导入 ,路由:根据不同的地址,展现不同的组件
    //比如 home组件
    import from './Home'
    import from './Login'  // 直接读了整个文件,但不会执行,只是加载了Login文件中的内容
    // 所以这就会存在一个问题,即使没有运行到该文件,这些资源也会加载
    
    import('./Home')  // 会始终返回promise
    
    // 及resolve不被调用的话,promise永远是pending状态
    // 只有进入对应路由后,才会进行调用
    
    
  1. cdn加速 :内容分发网络
  • 我们所有的依赖以及文件在我们进行打包后(build),会放到我们的服务器上面去
  • 那比如 依赖的 vue lodash vue-router等 ,也会被压缩成js
  • 那么 我们的服务器在深圳,你在纽约访问这个网站,就会去深圳拿资源,会很慢。
  • cdn加速 就是将我们依赖的第三方模块全部写成cdn的形式,然后保证我们自己代码的一个小体积(体积小,服务器和客户端的传输压力就会小)
  • 也可以使用vite-plugin-cdn-import
plugins:[
    viteCDNPlugin({
      modules:[
        {
          name:'lodash',
          var:'_',
          path:"cnd路径"
        }
      ]
    })
]

script与link知识点

scrpit标签的同步与异步加载

  1. 同步加载
<!-- 阻塞式加载 --> 
<script src="/static/jquery.js"></script> 
<script src="/static/app.js"></script> <!-- 必须等待jquery.js加载完成 -->

2. 异步加载

// 方式一:async
<!-- 独立执行,无顺序保证 --> 
<script src="/static/analytics.js" async></script> 
<script src="/static/ad.js" async></script> <!-- 可能与analytics.js并行执行 -->

//方式二:defer
<!-- 保持执行顺序,DOMContentLoaded前执行 --> 
<script src="/static/lib1.js" defer></script> 
<script src="/static/lib2.js" defer></script> <!-- 确保lib1先执行 -->

link标签的预加载与预获取

// 方式一:prefetch
// vue项目中:通过 Webpack 魔法注释
import(/* webpackPrefetch: true */ './CriticalComponent.vue')
// 生成 <link rel="prefetch"> 标签,利用浏览器空闲时间预加载

// 方式二:预获取 preload
import(/* webpackPreload: true */ './Fonts.css')

// 生成 <link rel="preload"> 标签,提升关键资源优先级
//注意
prefetch加载 运用了link标签,会全部加载,只不过最后才会加载prefetch

// 提前获取首屏资源:希望尽早加载该资源
<link ref="preload" href="xxxxx" as="script" />

// 提前获取非首屏资源:当浏览器在页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容

<link ref="prefetch" href="xxxx"  as="script" />