编程知识 cdmana.com

Vue项目性能优化

做了挺多个vue项目,开发过程中也遇到过很多问题,总结一下,

Vue代码层面

1、v-for 遍历应该为 item 添加 key,且避免同时使用 v-if

(1)官方推荐v-for 遍历应该为 item 添加 key属性

在列表数据进行遍历渲染时,需要为每一项 item 设置唯一 key 值,方便 Vue.js 内部机制精准找到该条列表数据。当 state 更新时,新的状态值和旧的状态值对比,较快地定位到 diff 。

(2)官方说明 v-for 遍历避免同时使用 v-if

v-for 比 v-if 优先级高,如果每一次都要遍历整个数组,由于中间计算对比会影响渲染速度,尤其是当之需要渲染很小一部分的时候,必要情况下可以替换成 computed 属性。

推荐:

<ul>
  <li
     v-for="book in books"
     :key="book.id">
     {{ book.name }}
 </li>
</ul>
computed: {
  activeBooks: function () {
    return this.books.filter(function (book) {
        return book.isActive
    })
  }
}

不推荐:

<ul>
  <li
     v-for="book in books"
     v-if="book.isActive"
     :key="book.id">
     {{ book.name }}
  </li>
</ul>

注意:如果一定要使用可以用template包裹起来

<ul>
  <template v-for="book in books" :key="book.id">
      <li v-if="book.isActive">
         {{ book.name }}
      </li>
  </template>
</ul>

2、computed 和 watch

computed:是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值
  • 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算。例如在表单操作的时候,输入或者选择值改变的时候,另外的值联动更改。
watch:更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作
  • 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

3、v-if 和 v-show

v-if 是 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show 就简单得多, 不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 display 属性进行切换。

所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景,比如tabs组件切换。

4、长列表性能优化

Vue 会通过 Object.defineProperty 对数据进行劫持,来实现视图响应数据的变化,然而有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要 Vue 来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,那如何禁止 Vue 劫持我们的数据呢?可以通过 Object.freeze 方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。

export default {
  data: () => ({
    books: []
  }),
  async created() {
    const books = await axios.get("/api/books");
    this.books = Object.freeze(books);
  }
};

5、优化无限列表性能

如果你的应用存在非常长或者无限滚动的列表,那么需要采用“窗口化”的技术来优化性能,只需要渲染少部分区域的内容,减少重新渲染组件和创建 dom 节点的时间。window.requestAnimationFrame方法可以设置延迟加载的功能点击看例子
或者可以参考 vue-virtual-scroll-list 和 vue-virtual-scroller项目的做法,
或者有例如ElementUI,这样的UI框架有单独的无限滚动列表组件

6、全局事件手动销毁,避免内存泄漏

我们在.vue文件注册的事件会在vue组件销毁的时候自动被清除掉,。但是如果在<script>标签内使用全局事件定义方法window.addEventListener去注册事件,考虑可能有内存泄漏,所以建议在.vue文件被注销的时候手动删除

beforeDestroy() {
  removeEventListener('click', this.handleClick, false)
}

7、图片资源懒加载

对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的提升,也提高了用户体验。在项目中使用 Vue 的 vue-lazyload 插件:

(1)安装插件

npm install vue-lazyload --save-dev;

(2)在入口文件 man.js 中引入并使用

import VueLazyload from 'vue-lazyload';

在vue 中使用

Vue.use(VueLazyload);
// 或者添加自定义选项
Vue.use(VueLazyload, {
    preLoad: 1.3,
    error: 'dist/error.png',
    loading: 'dist/loading.gif',
    attempt: 1
});

(3)在 vue 文件中将 img 标签的 src 属性直接改为 v-lazy ,从而将图片显示方式更改为懒加载显示:

<img v-lazy="/static/img/1.png">

8、路由懒加载,组件按需引入

Vue 是单页面应用,可能会有很多的路由引入 ,这样使用 webpcak 打包后的文件很大,当进入首页时,加载的资源过多,页面会出现白屏的情况,不利于用户体验。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了。这样会大大提高首屏显示的速度,但是可能其他的页面的速度就会降下来。

路由懒加载:

const Foo = () => import('./Foo.vue');
const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
});

一种辅助设计,更灵活控制路由(黑白名单模式),在文件夹内部统一处理文件,concat所有路由信息

const files = require.context('.', true, /\.js$/);

var subRouters = [];

files.keys().map((key) => {
    if (key === './index.js') return;
    // console.log(files(key).default);
    subRouters = subRouters.concat(files(key).default);
});
export default subRouters;

9、第三方插件的按需引入

很常用的配置,例如我们觉得有的UI组件不满足,要引用别的UI库的单个组件,按需引入是项目创建的时候必须的
(1)安装 babel-plugin-component :

npm install babel-plugin-component -D

(2)以elementUI为例,将 .babelrc 修改为:

{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

(3)在 main.js 中按需引入引入部分组件:

import Vue from 'vue';
import { Button, Select } from 'element-ui';
 Vue.use(Button)
 Vue.use(Select)

10、服务端渲染 SSR or 预渲染

服务端渲染是指 Vue 在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的 html 片段直接返回给客户端这个过程就叫做服务端渲染。

服务端渲染:

(1)服务端渲染的优点:

  • 更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
  • 更快的内容到达时间(首屏加载更快):SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;

(2)服务端渲染的缺点:

  • 更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
  • 更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源,因此如果你预料在高流量环境下使用,请准备相应的服务器负载,并明智地采用缓存策略。

预渲染:

如果你的项目的 SEO 和 首屏渲染是评价项目的关键指标,那么你的项目就需要服务端渲染来帮助你实现最佳的初始加载性能和 SEO。如果你的 Vue 项目只需改善少数营销页面(例如 /about, /contact等)的 SEO,那么你可能需要预渲染,在构建时 (build time) 简单地生成针对特定路由的静态 HTML 文件。优点是设置预渲染更简单,并可以将你的前端作为一个完全静态的站点。

使用 prerender-spa-plugin 就可以轻松地添加设置预渲染 。

Webpack 层面的优化

1、Webpack 对图片进行压缩

2.1、Webpack 对图片进行压缩

在 vue 项目中除了可以在 vue.config.js 中 url-loader 中设置 limit 大小来对图片处理,对小于 limit 的图片转化为 base64 格式,其余的不做操作。所以对有些较大的图片资源,在请求资源的时候,加载会很慢,我们可以用 image-webpack-loader来压缩图片:

(1)先安装 image-webpack-loader :

npm install image-webpack-loader --save-dev

(2)在 vue.config.js 中进行配置:

{
  test: /.(png|jpe?g|gif|svg)(?.*)?$/,
  use:[
    {
    loader: 'url-loader',
    options: {
      limit: 10000,
      name: utils.assetsPath('img/[name].[hash:7].[ext]')
      }
    },
    {
      loader: 'image-webpack-loader',
      options: {
        bypassOnDebug: true,
      }
    }
  ]
}

2、减少 ES6 转为 ES5 的冗余代码

Babel 插件会在将 ES6 代码转换成 ES5 代码时会注入一些辅助函数,例如下面的 ES6 代码:

class HelloWebpack extends Component{...}

这段代码再被转换成能正常运行的 ES5 代码时需要以下两个辅助函数:

babel-runtime/helpers/createClass  // 用于实现 class 语法
babel-runtime/helpers/inherits  // 用于实现 extends 语法

在默认情况下, Babel 会在每个输出文件中内嵌这些依赖的辅助函数代码,如果多个源代码文件都依赖这些辅助函数,那么这些辅助函数的代码将会出现很多次,造成代码冗余。为了不让这些辅助函数的代码重复出现,可以在依赖它们时通过 require('babel-runtime/helpers/createClass') 的方式导入,这样就能做到只让它们出现一次。babel-plugin-transform-runtime 插件就是用来实现这个作用的,将相关辅助函数进行替换成导入语句,从而减小 babel 编译出来的代码的文件大小。

(1)先安装 babel-plugin-transform-runtime :

npm install babel-plugin-transform-runtime --save-dev

(2)修改 .babelrc 配置文件为:

"plugins": [
    "transform-runtime"
]

3、tree-sharking

webpack5对tree-shaking支持的友好程度简直让人兴奋,对于webpack4也是基本够用了,不用多废话,上生产环境必须的,加就完了!!!

4、提取公共代码

如果项目中没有去将每个页面的第三方库和公共模块提取出来,则项目会存在以下问题:

  • 相同的资源被重复加载,浪费用户的流量和服务器的成本。
  • 每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验。

所以我们需要将多个页面的公共代码抽离成单独的文件,来优化以上问题 。Webpack 内置了专门用于提取多个Chunk 中的公共部分的插件 CommonsChunkPlugin,我们在项目中 CommonsChunkPlugin 的配置如下:

// 所有在 package.json 里面依赖的包,都会被打包进 vendor.js 这个文件中。
new webpack.optimize.CommonsChunkPlugin({
  name: 'vendor',
  minChunks: function(module, count) {
    return (
      module.resource &&
      /.js$/.test(module.resource) &&
      module.resource.indexOf(
        path.join(__dirname, '../node_modules')
      ) === 0
    );
  }
}),
// 抽取出代码模块的映射关系
new webpack.optimize.CommonsChunkPlugin({
  name: 'manifest',
  chunks: ['vendor']
})

5、模板预编译

当使用 DOM 内模板或 JavaScript 内的字符串模板时,模板会在运行时被编译为渲染函数。通常情况下这个过程已经足够快了,但对性能敏感的应用还是最好避免这种用法。

预编译模板最简单的方式就是使用单文件组件——相关的构建设置会自动把预编译处理好,所以构建好的代码已经包含了编译出来的渲染函数而不是原始的模板字符串。

如果你使用 webpack,并且喜欢分离 JavaScript 和模板文件,你可以使用 vue-template-loader,它也可以在构建过程中把模板文件转换成为 JavaScript 渲染函数。

6、提取组件的 CSS

当使用单文件组件时,组件内的 CSS 会以 style 标签的方式通过 JavaScript 动态注入。这有一些小小的运行时开销,如果你使用服务端渲染,这会导致一段 “无样式内容闪烁 (fouc) ” 。将所有组件的 CSS 提取到同一个文件可以避免这个问题,也会让 CSS 更好地进行压缩和缓存。

vue-cli 的 webpack 模板已经预先配置好

7、优化 SourceMap

我们在项目进行打包后,会将开发中的多个文件代码打包到一个文件中,并且经过压缩、去掉多余的空格、babel编译化后,最终将编译得到的代码会用于线上环境,那么这样处理后的代码和源代码会有很大的差别,当有 bug的时候,我们只能定位到压缩处理后的代码位置,无法定位到开发环境中的代码,对于开发来说不好调式定位问题,因此 sourceMap 出现了,它就是为了解决不好调式代码问题的。

SourceMap 的可选值如下(+ 号越多,代表速度越快,- 号越多,代表速度越慢, o 代表中等速度 )
image

开发环境推荐:cheap-module-eval-source-map

生产环境推荐:cheap-module-source-map

原因如下:

  • cheap:源代码中的列信息是没有任何作用,因此我们打包后的文件不希望包含列相关信息,只有行信息能建立打包前后的依赖关系。因此不管是开发环境或生产环境,我们都希望添加 cheap 的基本类型来忽略打包前后的列信息;
  • module :不管是开发环境还是正式环境,我们都希望能定位到bug的源代码具体的位置,比如说某个 Vue 文件报错了,我们希望能定位到具体的 Vue 文件,因此我们也需要 module 配置;
  • soure-map :source-map 会为每一个打包后的模块生成独立的 soucemap 文件 ,因此我们需要增加source-map 属性;
  • eval-source-map:eval 打包代码的速度非常快,因为它不生成 map 文件,但是可以对 eval 组合使用 eval-source-map 使用会将 map 文件以 DataURL 的形式存在打包后的 js 文件中。在正式环境中不要使用 eval-source-map, 因为它会增加文件的大小,但是在开发环境中,可以试用下,因为他们打包的速度很快。

8、构建结果输出分析

Webpack 输出的代码可读性非常差而且文件非常大,让我们非常头疼。为了更简单、直观地分析输出结果,社区中出现了许多可视化分析工具。这些工具以图形的方式将结果更直观地展示出来,让我们快速了解问题所在。分析工具:webpack-bundle-analyzer 。

我们在项目中 vue.config.js 进行配置:

if (config.build.bundleAnalyzerReport) {
  var BundleAnalyzerPlugin =   require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}

执行 $ npm run build --report 后生成分析报告

统一优化

1、浏览器缓存

为了提高用户加载页面的速度,对静态资源进行缓存是非常必要的,根据是否需要重新向服务器发起请求来分类,将 HTTP 缓存规则分为两大类(强制缓存,对比缓存)

2、CDN 的使用

静态外部文件太大,可以考虑使用稳定的CDN服务来加载资源,减少项目的体积

在vue.config.js配置里面添加如下配置

configureWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      // config.plugins.push(new BundleAnalyzerPlugin())
      config.externals = {
        lodash: '_',
        vue: 'Vue',
        'vue-router': 'VueRouter',
        vuex: 'Vuex',
        'element-ui': 'ELEMENT',
        axios: 'axios',
        highcharts: 'Highcharts',
        echarts: 'echarts',
        'v-charts': 'VeIndex',
        sortablejs: 'Sortable'
      }
    } else {
      config.plugins.push(
        new HardSourceWebpackPlugin({
          cacheDirectory: '../.cache/hard-source/[confighash]'
        })
      )
    }
  }

先在index.html文件引入CDN文件(包括配套的是js和css文件),然后在main.js文件引入相关的组件就行

<link rel="stylesheet" href="https://cdn.bootcss.com/element-ui/2.8.2/theme-chalk/index.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/v-charts/lib/style.min.css">


<script src="https://cdn.bootcss.com/vue/2.6.10/vue.runtime.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>
<script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<script src="https://cdn.bootcss.com/element-ui/2.8.2/index.js"></script>
<script src="https://cdn.bootcss.com/highcharts/7.1.1/highcharts.js"></script>
<script src="https://cdn.bootcss.com/highcharts/7.1.1/highcharts-3d.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/v-charts/lib/index.min.js"></script>

3、Chrome Performance 查找性能瓶颈

Chrome 的 Performance 面板可以录制一段时间内的 js 执行细节及时间。使用 Chrome 开发者工具分析页面性能的步骤如下。

  1. 打开 Chrome 开发者工具,切换到 Performance 面板
  2. 点击 Record 开始录制
  3. 刷新页面或展开某个节点
  4. 点击 Stop 停止录制

版权声明
本文为[万年打野易大师]所创,转载请带上原文链接,感谢
https://segmentfault.com/a/1190000038149459

Scroll to Top