之前在看到了一片文章,写的比较好,这里记录下自己的理解。
文章地址
Vue性能优化自己也一直在做,不过没有成体系的列出来,这里记录下,方便以后新项目或者新公司做系统的优化时有遗漏。
Vue代码层面优化
这里指的是Vue语法的使用,需要注意到的方面。
v-if以及v-show的灵活运用
- v-if决定元素是否会被渲染,如果需要频繁的切换v-if条件,则不建议使用,增加渲染的成本,适用于不会频繁切换的条件渲染,或者某些可能不需要加载的模块
- v-show决定元素是否展示,元素始终会渲染,只是单纯的display: none/block的样式切换,适用于频繁切换条件的地方
computed和watch灵活运用
- computed依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值,适用于做一个复杂的条件判断,或者数值计算,减少模板的语法复杂度
- watch监听的一个动作,我自认为适用于当属性发生变化时要做的一些通用操作,即当xxx我就yyy这一套固定的流程,有一种调用方法的意思,computed则更多的是属性的意思
v-for必添加key,避免同时使用v-if,避免使用index当做key值
- v-for添加key值可以提高diff的效率,避免整个list都重新做比较
- v-for优先级高于v-if,如果同时使用,则每个遍历的元素都会做一次条件判断,如果需要通过条件展示元素,可以使用computed把不展示的过滤掉再遍历
- 避免使用index作为key值,当list做插入新元素或者删除某元素时,该元素后面的元素的index值会全部被修改,key值也就全部被修改了,diff的时候会先寻找key对应的虚拟DOM,发现key值一样,但是其虚拟DOM则都不一样,那么相当于没有写key了
长列表性能优化
vue会对数据进行劫持,如果只是单纯的需要展示很多数据不做其他操作,可以使用 Object.freeze(obj) 来冻结一个对象,冻结之后就无法进行数据劫持和修改操作了。
组件销毁时注销事件和定时器
created() {
addEventListener('click', this.click, false)
this.timer = setInterval()
},
beforeDestroy() {
removeEventListener('click', this.click, false)
clearInterval(this.timer)
}
图片懒加载
常用的做法,主要是通过滚动条高度和屏幕高度判断当前图片是否需要整处于当前的视图上。这里我常用到的插件是vue-lazyload
npm install vue-lazyload --save-dev
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload)
Vue.use(VueLazyload, {
preLoad: 1.3,
error: 'dist/error.png',
loading: 'dist/loading.gif',
attempt: 1
})
// 指令方式使用
<img v-lazy="/static/img/1.png" alt="failed_pcture">
路由懒加载
- 不用懒加载,所有路由组件都会被编译进主包里
- 懒加载路由,进入当前路由时才加载对应的模块,首次加载会快很多,加载对应路由的模块时,也不会有太大的延迟,推荐使用
const router = new VueRouter({
routes: [
{ path: '/foo', component: () => import('./Foo.vue') }
]
})
三方库按需引入
可能有些三方库里面的组件我们永远都不会使用到,就按照Element来说,如果我们只需要用到button组件,那么自然不必整个引入Element:
// 安装
npm install babel-plugin-component -D
// 修改.babelrc
{
"presets": [["es2015", { "modules": false }]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
// main.js中引入
import Vue from 'vue';
import { Button } from 'element-ui';
Vue.use(Button)
基本的组件库都会提供按需引入的功能,具体的在库的文档中也会贴出对应的配置方法。
无限列表优化
类似懒加载图片,也是通过计算滚动条高度和屏幕高度来判断该渲染哪些数据,超出视图之外太远的数据可以直接不用渲染。对应的三方插件有:vue-virtual-scroll-list vue-virtual-scroller
服务端渲染
也就是SSR,服务器会将所有数据组装好再直接返回一个html,浏览器拿到就可以直接展示。
- 首屏加载可以更快,不用等待各种ajax请求
- 更好的SEO条件,ajax对爬虫不友好,用SSR可以规避
- 服务器压力会更大,开发时也需要处在node server的运行环境
webpack打包层面优化
小图片使用base64加载,雪碧图等
归根结底还是为了减少http请求
- 静态资源图片如果就几KB那么就没必要向服务器发起请求,可以使用webpack的image-webpack-loader来转成base64格式
- 雪碧图:把多个小图标放在一张图片中,通过background-postion属性来确定具体展示哪个图标
减少 ES6 转为 ES5 的冗余代码
babel会把ES6的代码转成ES5,这需要用到很多的辅助函数,如果多个模块都需要用到这些辅助函数,那么可以让他们只出现一次,使用插件:babel-plugin-transform-runtime:
// 安装
npm install babel-plugin-transform-runtime --save-dev
// .babelrc
"plugins": [
"transform-runtime"
]
抽离公用的代码
如果没有做处理:
- 相同的资源被重复加载,浪费用户的流量和服务器的成本。
- 每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验。
可以使用Webpack 内置的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']
})
build包代码分析
使用插件:webpack-bundle-analyzer
分支之后可能会发现一些问题,比如某位开发人员实现一个小功能引入一个比较大的库,如果没有code review会很难发现问题,或者一些遗留的无用代码,引入了但是并没有实际的作用,类似隐藏的废弃代码。
简单的配置如下:
// webpack.prod.conf.js中
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
抽离三方依赖
一般的Vue项目应该会存在几个依赖库:Vue、vue-router、vuex、axios、Element、echart、lodash,这些库打包进主包中,主包好几M是常有的事,首次加载也会奇慢无比,拆分出来有这些好处:
- 利用浏览器可以同时发起多个请求,利用它给主包减肥,6个1M的文件下载速度会远高于1个6M的文件,抽离之后首次加载速度会有肉眼可见的明显提升
- 依赖库不会经常改变,或者说一般不会被修改,缓存能更好的利用上,修改业务代码不会刷新这些文件的缓存
我们把这些三方库使用外部js引入:
// webpack.prod.conf.js
// 库文件使用cdn
externals: {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'axios': 'axios',
'element-ui': 'ELEMENT', // element的umd文件变量名叫ELEMENT
'echarts': 'echarts',
'lodash': '_',
},
// index.html -- 不影响渲染的库可以添加defer做延迟下载
<script src="cdn/vue.min.js"></script>
<script src="cdn/vuex.min.js"></script>
<script src="cdn/vue-router.min.js"></script>
<script src="cdn/axios.min.js"></script>
<script src="cdn/element/index.min.js"></script>
<script src="cdn/lodash.min.js" defer></script>
<script src="cdn/echarts.min.js" defer></script>
通用的优化技巧
服务端开启GZIP
GZIP可以打到70%左右的压缩率,如果你的网页有 30K,压缩之后就变成了 9K 左右,可以在浏览器看请求的响应头看到:
合理的使用缓存
合理的设置缓存过期时间和文件ETag,修改代码之后的版本号检测修改。甚至首屏也可以做数据缓存,接口信息也可以放在浏览器端做缓存。
使用CDN
CDN 具有更好的可用性,更低的网络延迟和丢包率 。静态资源都可以放进去。
CDN域名不宜过多,2-3个左右比较合适,更好的使用dns缓存,减少dns解析的成本。
减少全局变量的使用
- 拿chrome来说,全局变量不好被标记清楚,浏览器也不能够确定何时该做垃圾回收。
- 全局变量污染问题。
减少dom层级
dom层级过多会增加渲染成本,和虚拟DOM的diff的成本
重排(回流)和重绘问题
加载和更新视图时需要考虑到的问题,做动画时也需要考虑到,写替换性标签(img等)等也需要考虑到引发重排问题
避免使用CSS表达式
计算慢,耗性能