Skip to content
当前页导航

性能优化篇

webpack 优化

  1. 利用 optimization.splitChunks 根据具体规则进行拆分,提高首屏加载速度。 Vue2 项目可以在 package.json 中添加命令  "report": "vue-cli-service build --report" 。然后执行命令,就会在 dist 目录下生成一个 report.html 文件,浏览器中打开即可看到打包分析报告。然后就可以根据需要进行代码和第三方包的抽离。
js
const path = require('path')
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all', // all 对同步和异步代码都进行分割
      cacheGroups: {
        // libs: { // 最好是不要把 node_modules 里面的包都放在一起,会很大
        //   name: 'libs', // 文件导出名称
        //   test(module) {
        //     // 返回一个布尔值
        //     // `module.resource` 包含磁盘上文件的绝对路径。
        //     // 请注意使用 `path.sep` 而不是 / 或 \,以实现跨平台兼容性。
        //     return (
        //       module.resource &&
        //       module.resource.includes(`${path.sep}node_modules`)
        //     )
        //   },
        //   priority: -10, // 该规则的优先级,数字越大,当符合多个规则的时候,优先级越高,尽量用负数,因为默认是0
        //   minSize: 1, // 当文件大于此大小的时候,进行分割 ,实际开发中一般为 5 * 1024 ,即 5KB
        //   minChunks: 1,  // 最少被引用的次数,满足此条件就会进行分割
        // },
        echarts: {
          name: 'echarts-lib',
          test(module) {
            return (
              module.resource &&
              module.resource.includes(
                `${path.sep}node_modules${path.sep}echarts`
              )
            )
          },
          priority: -8,
          minSize: 1,
          minChunks: 1,
        },
      },
    },
  },
}
  1. 利用 externals 把一些第三方包进行外部扩展,通过 cdn 的方式引入,以减轻服务器压力。另外在多页面应用中,不同的页面可能依赖于不同的库。使用 externals 可以确保每个页面只包含它实际需要的库,避免不必要的重复打包。
js
// vue.config.js
module.exports = {
  configureWebpack: {
    externals: {
      // 把下面的第三方包全部提取到外部
      vue: 'Vue',
      'vue-router': 'VueRouter',
      vuex: 'Vuex',
      axios: 'axios',
      'element-ui': 'ELEMENT',
      echarts: 'echarts',
    },
  },
}

// main.js 可以不用引入 element
html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="referrer" content="no-referrer" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta
      http-equiv="cache-control"
      content="no-cache, no-store, must-revalidate"
    />
    <meta
      name="viewport"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <title>深圳</title>
    <link rel="stylesheet" href="https://xxx.com/lib/element-ui@2.13.2.css" />
  </head>
   
  <body>
    <div id="app"></div>

    <script src="https://xxx.com/lib/vue@2.6.14.min.js"></script>
    <script src="https://xxx.com/lib/vuex@3.4.0.min.js"></script>
    <script src="https://xxx.com/lib/vue-router@3.2.0.min.js"></script>
    <script src="https://xxx.com/lib/axios@0.20.0.min.js"></script>
    <script src="https://xxx.com/lib/echarts@4.9.0.min.js"></script>
    <script src="https://xxx.com/lib/element-ui@2.13.2.js"></script>
  </body>
</html>
  • dns-prefetch :要求浏览器提前执行指定网址的 DNS 查询。
html
<link rel="dns-prefetch" href="https://example.com/" />
  • preconnect :要求浏览器提前与给定服务器,建立 HTTP 连接。当你知道,很快就会请求该域名时,这会很有帮助。
html
<link rel="preconnect" href="https://example.com/" />
  • prerender :要求浏览器提前渲染指定链接。这样的话,用户稍后打开该链接,就会立刻显示,感觉非常快。如果确定用户下一步会访问该页面,这会很有帮助。
html
<link rel="prerender" href="https://example.com/" />
  • prefetch :要求浏览器提前下载并缓存指定资源,供下一个页面使用,没法指定预加载资源的类型。下载后浏览器不会对资源执行任何操作,脚本未执行,样式表未应用,不会阻塞浏览器线程。它的优先级较低,浏览器可以不下载。这意味着,浏览器可以不下载该资源,比如连接速度很慢时。
html
<link rel="prefetch" href="https://example.com/1.js" />
<link rel="prefetch" href="https://example.com/1.css" />
  • preload :要求浏览器提前下载并缓存指定资源,当前页面稍后就会用到,必须指定预加载资源的类型。下载后浏览器不会对资源执行任何操作,脚本未执行,样式表未应用,不会阻塞浏览器线程。它的优先级较高,浏览器必须立即下载。它只是缓存,当其他地方需要它时,它立即可用。
    • 可以用来预加载比较大的图片资源。
html
<link rel="preload" href="https://example.com/1.js" as="script" />
<link rel="preload" href="https://example.com/1.css" as="style" />
<link rel="preload" href="https://example.com/1.png" as="image" />
<link rel="preload" href="https://example.com/1.mp4" as="video" />
<link rel="preload" href="https://example.com/1.woff2" as="font" />

动态加载非公共文件、html 标签引入的第三方 js 和 css 文件

一些需要使用 script 和 link 标签引入的第三方文件,且不是公共文件,可以在对应的页面进行动态加载。在加载完成后,再去执行页面其他代码。通过监听 script、link 标签的 onload 事件实现。

js
// 封装动态加载的方法
function loadFile(list, groupId, cb) {
  // groupId 用于判断当前整组的文件是否已加载过
  // {
  //   url: '链接地址',
  //   type: '文件类型'
  // }
  const a = document.getElementById(groupId)
  if (!!a) {
    setTimeout(() => {
      cb && cb()
    }, 500)
    return false
  }
  let link = null
  const len = list.length
  for (const i in list) {
    const item = list[i]
    if (item.type === 'js') {
      link = document.createElement('script')
      link.setAttribute('type', 'text/javascript')
      link.setAttribute('src', item.url)
    } else if (item.type === 'css') {
      link = document.createElement('link')
      link.setAttribute('rel', 'stylesheet')
      link.setAttribute('type', 'text/css')
      link.setAttribute('href', item.url)
    }
    if (i == len - 1) {
      link.id = groupId
    }
    if (link) {
      document.getElementsByTagName('head')[0].appendChild(link)
      if (i == len - 1) {
        link.onload = () => {
          cb && cb()
        }
      }
    }
  }
}

// 使用
util.loadFile(
  [
    {
      url: 'https://xxx.com/Cesium/Widgets/widgets.css',
      type: 'css',
    },
    {
      url: 'https://xxx.com/Cesium/Cesium.js',
      type: 'js',
    },
  ],
  '#DsuperMap',
  () => {
    console.log('加载完成')
  }
)

其他优化

  • Vue 路由动态加载。
  • 对于一些组件可以异步加载,仅在页面需要它渲染时才会调用加载内部实际组件的函数。
    • vue2 通过 import
    js
    export default {
      components: {
        MyComponent: () => import('./MyComponent.vue'),
      },
    }
    • vue3 通过 defineAsyncComponent
    js
    import { defineAsyncComponent } from 'vue'
    const AdminPage = defineAsyncComponent(() =>
      import('./components/AdminPageComponent.vue')
    )
    html
    <template>
      <AdminPage />
    </template>
  • vue 对于一些组件初始化之后,里面的数据不会再进行变更,可以利用 v-oncev-memo 进行优化。
    • v-memo 接收一个数组,空数组则跟 v-once 一样,数组有值的话,里面的内容都保持不变,则对应的内容不会重新渲染。
html
<!-- 单个元素 -->
<span v-once>This will never change: {{msg}}</span>
<!-- 带有子元素的元素 -->
<div v-once>
  <h1>Comment</h1>
  <p>{{msg}}</p>
</div>
<!-- 组件 -->
<MyComponent v-once :comment="msg" />
<!-- `v-for` 指令 -->
<ul>
  <li v-for="i in list" v-once>{{i}}</li>
</ul>
html
<div v-memo="[valueA, valueB]"></div>
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
  <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
  <p>...more child nodes</p>
</div>
  • 对于一些分页列表,如果这个列表是纯展示,不会变更列表里的任何数据。

    • vue2 可以通过 Object.freeze 冻结属性,这样就不会把数据变成响应式了。
    js
    const list = await fetch('http:xxxxxxx')
    this.tableList = Object.freeze(list)
    • vue3 可以通过 shallowRef 创建浅层的响应式数据,它不会把内部数据都变成响应式的,减少响应式开销。
    js
    const state = shallowRef({ count: 1 })
    const list = shallowRef([{ name: 1 }])
    
    // 不会触发更改
    state.value.count = 2
    list.value[0].name = 'tom'
    
    // 会触发更改
    state.value = { count: 2 }
    list.value = [{ name: 3 }]
  • 开启 Gzip。

  • 一些较大的图片使用 webp 格式。

  • 采用 keep-alive 缓存组件

  • vue 中的 key 不要使用索引,方便 diff

  • 防抖、节流

  • 长列表优化,使用虚拟列表