面试题(六):前端常用的性能优化方案

38 阅读5分钟

主要目的

让页面加载更快(需要让代码打包后的体积尽可能的小以及让资源请求时机分配更合理)

性能分析工具

lighthouse

谷歌浏览器自带的开发者工具,在项目部署上线后可以f12找到lighthouse对项目进行跑分,lighthouse不仅能通过评分直观的看到项目的整体性能情况,还能提出项目的性能问题,可以依照提出的问题对项目进行优化

Webpack Bundle分析

webpack中提供了对打包的项目文件体积直观展示的插件webpack-bundle-analyzer,安装后在vue.config.js中配置,然后在执行了打包命令打包结束后就会自动打开浏览器展示打包的各文件体积,可以直观的看到哪些文件体积过大,也能看到哪些依赖包项目并没有用到却忘了移除,可以通过这个插件去优化项目的包体积

// vue.config.js配置
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
    configureWebpack: {
        plugins: [
            new BundleAnalyzerPlugin()
        ],
    },
}

开发阶段

开发过程中按需引入、懒加载、请求合理化、代码精简等

打包阶段

  • 配置图片压缩以及gzip压缩

vue-cli配置图片压缩以及gzip压缩:

module.exports = {
  // 修改已有的plugins/loader时使用chainWebpack
  chainWebpack: config => {
    // 压缩图片
    // 需要 npm i -D image-webpack-loader
    config.module
      .rule("images")
      .use("image-webpack-loader")
      .loader("image-webpack-loader")
      .options({
        mozjpeg: { progressive: true, quality: 65 },
        optipng: { enabled: false },
        pngquant: { quality: [0.65, 0.9], speed: 4 },
        gifsicle: { interlaced: false },
        webp: { quality: 75 }
      });
  },
  
  // 添加新的plugins/loader以及webpack配置项时使用configureWebpack
  configureWebpack: config => {
    // 开启 gzip 压缩
    // 需要 npm i -D compression-webpack-plugin
    const plugins = [];
    if (IS_PROD) {
      plugins.push(
        new CompressionWebpackPlugin({
          filename: "[path].gz[query]",
          algorithm: "gzip",
          test: productionGzipExtensions,
          threshold: 10240,
          minRatio: 0.8
        })
      );
    }
    config.plugins = [...config.plugins, ...plugins];
  }
}

vite配置图片压缩、gzip压缩、移除console.log:

    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    //引入gzip压缩
    import viteCompression from 'vite-plugin-compression'
    //压缩图片
    import viteImagemin from 'vite-plugin-imagemin'
    
    // https://vitejs.dev/config/
    export default defineConfig({
      //配置打包公共根路径
      base: "./",
      plugins: [
        vue(),
        //gzip压缩
        viteCompression({
          verbose: true,
          disable: false,
          threshold: 10240,
          algorithm: 'gzip',
          ext: '.gz',
        }),
        //图片压缩
        viteImagemin({
          gifsicle: {
            optimizationLevel: 7,
            interlaced: false
          },
          optipng: {
            optimizationLevel: 7
          },
          mozjpeg: {
            quality: 20
          },
          pngquant: {
            quality: [0.8, 0.9],
            speed: 4
          },
          svgo: {
            plugins: [
              {
                name: 'removeViewBox'
              },
              {
                name: 'removeEmptyAttrs',
                active: false
              }
            ]
          }
        })
      ],
      //生产环境配置
      build: {
        //移除console.log
        minify: 'terser', // 默认为esbuild,需要安装terser -D
        terserOptions: {
          compress: {
            // drop_console: true, // 生产环境移除console
            // drop_debugger: true // 生产环境移除debugger
          }
        }
      }
    })

  • 分包

vue-cli3的默认优化是将所有npm依赖都打进chunk-vendor,但这种做法在依赖多的情况下导致chunk-vendor过大,可以在vue.config.js中配置分包策略,将第三方依赖包单独打包,避免打包后单一文件过大导致请求加载缓慢。

    optimization: isProd ? {
      splitChunks: {
        chunks: 'all',
        maxInitialRequests: Infinity, // 默认为3,调整为允许无限入口资源
        minSize: 20000, // 20K以下的依赖不做拆分
        cacheGroups: {
          vendors: {
            // 拆分依赖,避免单文件过大拖慢页面展示
            // 得益于HTTP2多路复用,不用太担心资源请求太多的问题
            name(module) {
              // 拆包
              const packageName = module.context.match(/[\/]node_modules[\/](.*?)([\/]|$)/)[1]
              // 进一步将Ant组件拆分出来,请根据情况来
              // const packageName = module.context.match(/[\/]node_modules[\/](?:ant-design-vue[\/]es[\/])?(.*?)([\/]|$)/)[1]
              return `npm.${packageName.replace('@', '')}` // 部分服务器不允许URL带@
            },
            test: /[\/]node_modules[\/]/,
            priority: -10,
            chunks: 'initial'
          }
        }
      },
      runtimeChunk: { name: entrypoint => `runtime-${entrypoint.name}` }
    } : {}

  • 资源预加载/预请求

标签的rel属性的两个可选值。Prefetch,预请求,是为了提示浏览器,用户未来的浏览有可能需要加载目标资源,所以浏览器有可能通过事先获取和缓存对应资源,优化用户体验。Preload,预加载,表示用户十分有可能需要在当前浏览中加载目标资源,所以浏览器必须预先获取和缓存对应资源。

部署阶段

服务器部署项目用nginx开启http2、静态资源缓存、资源压缩等减少项目包的请求时间和次数等

  • 开启http2

http2有着多路复用和头部信息压缩的特性,项目开启http2可以同时处理多个请求,加快请求时间,开启http2之前需要给项目开启https

  • 静态资源缓存

nginx还可以配置静态资源缓存,对项目的静态资源配置缓存后在资源第一次请求后会将资源缓存下来,后续就可以直接使用缓存,在缓存到期之前都不会再次请求该静态资源,可以加快资源获取时间

#静态资源缓开启缓存
    location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$
    {
        # 这里同样需要反向代理,不然请求路径会是haixtx.cn:82,会出现404
        proxy_pass http://114.55.75.3:8000;
        expires 30d;
    }
    location ~ .*.(js|css)?$
    {
        proxy_pass http://114.55.75.3:8000;
        expires 12h;
    }

  • gzip压缩

nginx也可以配置gzip压缩,HTTP协议中用头部字段Accept-EncodingContent-Encoding对「采用何种编码格式传输正文」进行了协定,请求头的Accept-Encoding会列出客户端支持的编码格式。当响应头的Content-Encoding指定了gzip时,浏览器则会进行对应解压

一般浏览器都支持gzip,所以Accept-Encoding也会自动带上gzip,所以我们需要让资源服务器在Content-Encoding指定gzip,并返回gzip文件

    #开启和关闭gzip模式
    gzip on;
    #gizp压缩起点,文件大于1k才进行压缩
    gzip_min_length 1k;
    # gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间
    gzip_comp_level 6;
    # 进行压缩的文件类型。
    gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript ;
    # nginx对于静态文件的处理模块,开启后会寻找以.gz结尾的文件,直接返回,不会占用cpu进行压缩,如果找不到则不进行压缩
    gzip_static on
    # 是否在http header中添加Vary: Accept-Encoding,建议开启
    gzip_vary on;
    # 设置gzip压缩针对的HTTP协议版本
    gzip_http_version 1.1;

虽然上面配置后Nginx已经会在响应请求时进行压缩并返回Gzip了,但是压缩操作本身是会占用服务器的CPU和时间的,压缩等级越高开销越大,所以我们通常会一并上传gzip文件,让服务器直接返回压缩后文件,所以还需要在项目打包阶段配置gzip,具体配置详见上述