Vue项目性能优化方案
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:<img v-lazy="/static/img/logo.png">
6、路由懒加载:const comp = () => import('./Comp.vue')
7、第三方插件的按需引入
8、vue优化无限列表性能:使用vue-virtual-scroll-list插件
安装:npm install vue-virtual-scroll-list --save-dev
使用:<virtual-list style="height: 660px; overflow-y: auto;" :data-key="'id'" :data-sources="itemData" :data-component="itemComponent" /> (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 compression-webpack-plugin@6.1.1 --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钩子中返回当前页面
// 断网页面
<template>
<div id="app">
<div v-if="!network">
<h3>我没网了</h3>
<div @click="onRefresh">刷新</div>
</div>
<router-view/>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
name: 'App',
computed: {
...mapState(['network'])
},
methods: {
// 通过跳转一个空页面再返回的方式来实现刷新当前页面数据的目的
onRefresh () {
this.$router.replace('/refresh')
}
}
}
</script>
在refresh.vue页面中:
// refresh.vue
beforeRouteEnter (to, from, next) {
next(vm => {
vm.$router.replace(from.fullPath)
})
}
更多推荐
所有评论(0)