Vue项目性能优化方案

一、代码层面的优化

 1、v-if和v-show的使用场景
 (1)v-if适用于在运行时很少改变条件,不需要频繁切换条件的场景,属于真正的条件渲染,会确保在切换过程中,元素适当的被销毁和重建,也是惰性的;
 (2)v-show适用于频繁切换条件的场景,v-show不管初始的渲染条件是什么,元素总是会被渲染,然后根据css的display属性的值去切换显示与隐藏。

2、computed和watch的使用场景
 (1)当需要进行数值计算,并且依赖于其他数据时,应该使用computed,可以利用computed的缓存特性,避免每次获取值的时候都重新计算;
 (2)当需要在数据变化时执行异步或开销较大的操作时,应该是用watch,使用watch允许我们进行异步操作,限制我们执行该操作的频率,并且在我们得到最终结果前,设置中间值,能拿到每一次的当前值和前一次的值。

 3、v-for循环遍历必须为item添加key,key不建议设置为索引,且避免在同一个元素中同时使用v-if

4、长列表性能优化
 vue会通过Object.defineProperty()对数据进行劫持,来实现响应数据的变化,但是有时候我们的组件只需要实现对数据的展示就可以了,不会有其他修改删除的操作,所以就不需要vue来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,可以通过Object.freeze()方法来冻结一个对象,一旦被冻结的对象就不可以被修改

5、图片资源懒加载:使用vue-lazyload插件
安装:npm install vue-lazyload --save-dev
main.js引入:import VueLazyload from 'vue-lazyload'
使用:Vue.use(VueLazyload)
页面中使用,直接将img的src属性改成v-lazy:

6、路由懒加载:const comp = () => import('./Comp.vue')

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

8、vue优化无限列表性能:使用vue-virtual-scroll-list插件
安装:npm install vue-virtual-scroll-list --save-dev
使用: (itemData:数据,itemConponent:真正展示内容的组件)

9、keep-alive的使用
利用keep-alive将不活动的组件缓存起来,在组价切换过程中,将组件的状态保存在内存中,防止重复渲染dom,减少加载时间及性能消耗,提高用户体验。

二、webpack层面的优化

1、对图片进行压缩
在 vue 项目中除了可以在 webpack.base.conf.js 中 url-loader 中设置 limit 大小来对图片处理,对小于 limit 的图片转化为 base64 格式,其余的不做操作。所以对有些较大的图片资源,在请求资源的时候,加载会很慢,我们可以用 image-webpack-loader来压缩图片;
安装:npm install image-webpack-loader --save-dev
webpack.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代码的时候注入一些辅助函数,例如: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编译出来的代码的大小;
安装:npm install babel-plugin-transform-runtime --save-dev
修改.babelrc配置文件:

"plugins": [
    "transform-runtime"
]


3、提取公共代码,业务代码和第三方库代码分开:webpack3中使用CommonsChunkPlugin插件、webpack4中使用optimization.splitChunks插件

CommonsChunkPlugin插件详细参数:
name: 给这个包含公共代码的chunk命名
filename: 命名打包后生成的js文件
minChunks: 公共代码的判断标准,某个js模块被多少个文件引入才算公共代码,默认为1,我们可以为2, 也就是说如果
一个文件被其他页面引入了超过了2次及以上,就可以认为该文件就是公用代码。
chunks: 表示需要在哪些chunk(配置中entry的每一项)里寻找公共代码进行打包,默认不设置,那么它的寻找范围为所有的chunk;

如果项目中没有把每个页面的第三方库和公共模块提取出来,则项目会存在以下问题:
a、相同的资源被重复加载,浪费客户的流量和服务器的成本
b、每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验
所以我们需要将多个页面的公共代码抽离成单独的文件,来优化以上问题,webpack专门内置了用于提取多个chunk中的公共部分的插件CommonsChunkPlugin,我们在项目中CommonChunkPlugin的配置如下:

// 所有在 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']
})

optimization.splitChunks参数详解:

(1)、cacheGroups:splitChunks配置的核心,对代码的拆分规则全在cacheGroups缓存组里配置,缓存组里的每一个属性都是一个配置规则,可以自己定义属性名称,属性值是一个对象,放的是对一个代码拆分规则的描述,有两个属性:name和chunks;
name:提取出来的公共模块将会以这个名称命名,可以不配置,如果不配置的话,就会生成默认的文件名
chunks:指定哪些类型的chunks参与拆分,值可以是string也可以是函数,如果是string的话,可以有三个值:all、async、initial,all代表所有模块,async代表异步加载的,initial代表初始化时就能获取的模块,如果是函数,则可以通过chunks的name属性等进行更详细的筛选

(2)、miniChunks:splitChunks是自带默认配置的,而缓存组默认会继承这些配置,其中有个miniChunks属性,它是控制每个模块什么时候被抽离出去:当模块被不同的entry引用的次数大于等于这个配置的值时,才会被抽离出去,默认值是1,也就是任何模块都会被抽离出去,(入口模块也会被webpack引入一次)

(3)、miniSize:提取的chunk的最小大小

(4)、priority:值为数值型,可以是负数,作用是当拆分组中设置多个拆分规则,且某个模块符合多个规则时,则通过优先级属性priority来决定使用哪个拆分规则,优先级高者执行

(5)、test:用于进一步控制缓存组选择的模块,可以是一个正则表达式,也可以是一个函数,可以匹配模块的绝对路径或chunk名称,匹配chunk名称时将选择chunk中所有的模块

例子:

optimization: {
    splitChunks: {
        minSize: 30,  //提取出的chunk的最小大小
        cacheGroups: {
            default: {
                name: 'common',
                chunks: 'initial',
                minChunks: 2,  //模块被引用2次以上的才抽离
                priority: -20
            },
            vendors: {  //拆分第三方库(通过npm|yarn安装的库)
                test: /[\\/]node_modules[\\/]/,
                name: 'vendor',
                chunks: 'initial',
                priority: -10
            }
        }
    }
}

4、构建结果输出分析:webpack-bundle-analyzer
webpack输出的代码可读性很差,而且文件非常大,为了简单、直观的分析输出结果,可以使用分析工具,以图形的形式将结果更直观的展示出来
安装:npm install --save-dev webpack-bundle-analyzer
webpack.config.js配置:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
plugins: [
    new BundleAnalyzerPlugin()
    ]


package.json的"scripts"中添加:

"dev": "webpack --config webpack.config.js --progress"


执行语句生成分析报告:npm run dev

5、打包去掉console.log等语句:uglifyjs-webpack-plugin
安装:npm install uglifyjs-webpack-plugin
webpack.config.js中配置:

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
    configureWebpack: (config) => {
        if (process.env.NODE_ENV === 'production') {
            return {
                plugins: [
                    //打包环境去掉console.log
                    new UglifyJsPlugin({
                        uglifyOptions: {
                            compress: {
                                warnings: false,
                                drop_console: true,  //注释console
                                drop_debugger: true, //注释debugger
                                pure_funcs: ['console.log'], //移除console.log
                            },
                        },
                    }),
                ],
            }
        }
    }
}

三、基础的web技术优化

1、开启gzip压缩:compression-webpack-plugin
安装://新版本不太兼容,推荐这个版本
npm install [email protected] --save-dev
在webpack.config.js中使用:

const CompressionWebpackPlugin = require('compression-webpack-plugin')
new CompressionWebpackPlugin({
      filename: '[path][base].gz',
      algorithm: 'gzip',  // 压缩算法,官方默认压缩算法是gzip
      test: /\.js$|\.css$|\.html$|\.eot$|\.woff$/, // 使用gzip压缩的文件类型
      threshold: 10240,  // 以字节为单位压缩超过此大小的文件,默认是10240
      minRatio: 0.8,  // 最小压缩比率,默认是0.8
    })


后端配置:
(1)Nginx:主要是下方的gizp配置哦,直接复制粘贴就可以使用啦,亲测有效哦

server{
    //开启和关闭gzip模式
    gzip on;
    //gizp压缩起点,文件大于2k才进行压缩;设置允许压缩的页面最小字节数,页面字节数从header头得content-length中进行获取。 默认值是0,不管页面多大都压缩。建议设置成大于2k的字节数,小于2k可能会越压越大。
    gzip_min_length 2k;
    // 设置压缩所需要的缓冲区大小,以4k为单位,如果文件为7k则申请2*4k的缓冲区 
    gzip_buffers 4 16k;
    // 设置gzip压缩针对的HTTP协议版本
    gzip_http_version 1.0;
    // gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间
    gzip_comp_level 2;
    //进行压缩的文件类型
    gzip_types text/plain application/javascript text/css application/xml;
    // 是否在http header中添加Vary: Accept-Encoding,建议开启
    gzip_vary on;
}


(2)express的中间件:compression,主要用于进行gzip压缩,通常用于web性能优化,能对文本内容进行压缩,一般用于html文件;
安装:npm install compression --save
使用:

var compression = require('compression');
const app = express()
// 启用gzip
app.use(compression());

2、浏览器缓存
对静态资源进行缓存,根据是否需要重新向服务器发起请求来分类,将http请求分为强制缓存和对比缓存

3、CDN的使用
浏览器从服务器上下载css、js和图片等文件时都要和服务器连接,而大部分的服务器的带宽都有限,如果超过限制,网页就半天反应不过来,而CDN可以通过不同的域名来加载文件,从而使下载文件的并发连接数大大增加,且CDN具有更好的可用性,更低的网络延迟和丢包率

4、使用Chrome Performance查找性能瓶颈
Chrome的Performance面板可以录制一段时间内的js执行细节及时间,步骤:
a、打开Chrome开发者工具,点进Performance面板
b、点击Record开始录制
c、刷新页面或展开某个节点
d、点击stop停止录制,就可以查看了

四、网络层面的优化

1、重复请求的取消
第一次请求还没回来之前就进行了第二次同样的请求时,就应该把第二次请求取消掉,防止重复请求;
在axios请求中进行封装,采用拦截器进行处理,首先实现三个方法,当请求方式、请求URL和携带参数都一致时,就可以认为是同样的请求,因此在发起请求时,可以根据当前的请求方式、请求地址和携带的参数来生成一个唯一的key,同时为每个请求创建一个专属的cancelToken,然后将key和cancelToken函数以键值对的形式保存到map对象中,使用map对象的好处就是可以快速判断是否有重复的请求:

    // 用于根据当前的请求信息生成请求的key
    generateReqKey(config) {
      const { url, method, params, data } = config;
      return [url, method, qs.stringify(params), qs.stringify(data)].join("&");
    },
    // 用于将当前请求信息添加到pendingRequest对象中
    PendingRequest = new Map(),
    addPendingRequest(config) {
      const requestKey = this.generateReqKey(config)
      config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
        if(!this.PendingRequest.has(requestKey)) {
          this.PendingRequest.set(requestKey, cancel)
        }
      })
    },
    // 检查是否有重复的请求,若存在则需要取消已经发出的请求
    removeRequest(config) {
      const requestKey = this.generateReqKey(config)
      if (this.PendingRequest.has(requestKey)) {
        const CancelToken = this.PendingRequest.get(requestKey)
        CancelToken(requestKey)
        this.PendingRequest.delete(requestKey)
      }
    }

因为需要对所有的请求都进行处理,所以直接在拦截器来实现取消重复请求的操作,
(1)请求拦截器:作用是在请求发送前统一执行某些操作,比如在请求头中添加token字段
(2)响应拦截器:作用是在接收到服务器响应后统一执行某些操作,比如根据不同的状态码进行不同的响应操作

    axios.interceptors.request.use(config => {
      // 检查是否有重复的请求
      this.removeRequest(config)
      // 把当前请求添加到PendingRequest对象中
      this.addPendingRequest(config)
      const token = localStorage.getItem('token')
      if(token) {
        config.headers.Authorization = `Bearer ${token}`
      }
      return config
    }, error => {
      return Promise.reject(error)
    })
    axios.interceptors.response.use(res => {
      // 从PendingRequest对象中移除请求
      this.removeRequest(res.config)
      const {data} = config;
      return data
    }, error => {
      // 从PendingRequest对象中移除请求
      this.removeRequest(error.config)
      return Promise.reject(error)
    })

2、限制失败请求重试的次数,避免无效请求过多,导致大量的资源消耗

//在main.js设置全局的请求次数,请求的间隙
axios.defaults.retry = 4;  // 请求次数
axios.defaults.retryDelay = 1000;  // 请求间隙
//响应拦截
axios.interceptors.response.use(undefined, function axiosRetryInterceptor(err) {
    var config = err.config;
    // 如果配置不存在或未设置重试选项,则返回错误信息
    if(!config || !config.retry) return Promise.reject(err);
 
    // 设置变量即跟踪重试次数
    config.__retryCount = config.__retryCount || 0;
 
    // 检查我们是否已经超过了总重试次数
    if(config.__retryCount >= config.retry) {
        // 返回错误信息
        return Promise.reject(err);
    }
 
    // 重试次数加1
    config.__retryCount += 1;
 
    // 创建延时器等待发送重试请求
    var backoff = new Promise(function(resolve) {
        setTimeout(function() {
            resolve();
        }, config.retryDelay || 1);
    });
 
    // 返回调用AXIOS来重试请求
    return backoff.then(function() {
        return axios(config);
    });
});

3、善用缓存,减小网络请求的次数
4、断网处理:
在vuex中存放network的状态,根据network的状态判断是否需要加载断网组件,断网情况就加载断网组件,不加载对应的页面组件,点击刷新,就跳转到refresh页面然后立即返回来实现重新获取数据,所以新建refresh.vue,在beforeRouteEnter钩子中返回当前页面
 

// 断网页面



在refresh.vue页面中:
// refresh.vue
beforeRouteEnter (to, from, next) {
    next(vm => {            
        vm.$router.replace(from.fullPath)        
    })    
}

你可能感兴趣的:(前端,性能优化,vue.js)