Vue 框架经过数据双向绑定和虚拟 DOM 技术,帮咱们处理了前端开发中最脏最累的 DOM 操做部分, 咱们再也不须要去考虑如何操做 DOM 以及如何最高效地操做 DOM;但 Vue 项目中仍然存在项目首屏优化、Webpack 编译配置优化等问题,因此咱们仍然须要去关注 Vue 项目性能方面的优化,使项目具备更高效的性能、更好的用户体验。本文是做者经过实际项目的优化实践进行总结而来,但愿读者读完本文,有必定的启发思考,从而对本身的项目进行优化起到帮助。本文内容分为如下三部分组成:html
v-if是 真正 的条件渲染,由于它会确保在切换过程当中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:若是在初始渲染时条件为假,则什么也不作——直到条件第一次变为真时,才会开始渲染条件块。前端
v-show 就简单得多, 无论初始条件是什么,元素老是会被渲染,而且只是简单地基于 CSS 的 display 属性进行切换。vue
因此,v-if 适用于在运行时不多改变条件,不须要频繁切换条件的场景;v-show 则适用于须要很是频繁切换条件的场景。node
computed:是计算属性,依赖其它属性,而且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会从新计算 computed 的值;webpack
watch:更多的是 观察 的做用,相似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操做:ios
在列表数据进行遍历渲染时,须要为每一项 item 设置惟一 key 值,方便 Vue.js 内部机制精准找到该条列表数据。当 state 更新时,新的状态值和旧的状态值对比,较快地定位到 diff。web
v-for 比 v-if 优先级高,若是每一次都须要遍历整个数组,将会影响速度,尤为是当之须要渲染很小一部分的时候,必要状况下应该替换成 computed 属性。vue-cli
<ul>
<li
v-for="user in activeUsers"
:key="user.id">
{{ user.name }}
</li>
</ul>
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}
复制代码
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id">
{{ user.name }}
</li>
</ul>
复制代码
Vue 会经过 Object.defineProperty 对数据进行劫持,来实现视图响应数据的变化,然而有些时候咱们的组件就是纯粹的数据展现,不会有任何改变,咱们就不须要 Vue来劫持咱们的数据,在大量数据展现的状况下,这可以很明显的减小组件初始化的时间,那如何禁止 Vue 劫持咱们的数据呢?能够经过 Object.freeze 方法来冻结一个对象,一旦被冻结的对象就不再能被修改了。express
export default { data: () => ({ users: {} }), async created() { const users = await axios.get("/api/users"); this.users = Object.freeze(users); } }; 复制代码
Vue 组件销毁时,会自动清理它与其它实例的链接,解绑它的所有指令及事件监听器,可是仅限于组件自己的事件。 若是在 js 内使用 addEventListene 等方式是不会自动销毁的,咱们须要在组件销毁时手动移除这些事件的监听,以避免形成内存泄露,如:npm
created() { addEventListener('click', this.click, false) }, beforeDestroy() { removeEventListener('click', this.click, false) } 复制代码
对于图片过多的页面,为了加速页面加载速度,因此不少时候咱们须要将页面内未出如今可视区域内的图片先不作加载, 等到滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的提高,也提升了用户体验。咱们在项目中使用 Vue 的 vue-lazyload 插件:
(1) 安装插件:
npm install vue-lazyload --save-dev
复制代码
(2)再入口文件 main.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"> 复制代码
Vue 是单页面应用,可能会有不少的路由引入 ,这样使用 webpcak 打包后的文件很大,当进入首页时,加载的资源过多,页面会出现白屏的状况,不利于用户体验。若是咱们能把不一样路由对应的组件分割成不一样的代码块,而后当路由被访问的时候才加载对应的组件,这样就更加高效了。这样会大大提升首屏显示的速度,可是可能其余的页面的速度就会降下来。
路由懒加载:
const Foo = () => import('./Foo.vue') const router = new VueRouter({ routes: [ { path: '/foo', component: Foo } ] }) 复制代码
咱们在项目中常常会须要引入第三方插件,若是咱们直接引入整个插件,会致使项目的体积太大,咱们能够借助 babel-plugin-component ,而后能够只引入须要的组件,以达到减少项目体积的目的。如下为项目中引入 element-ui 组件库为例:
(1) 首先,安装 babel-plugin-component:
npm install babel-plugin-component -D
复制代码
(2) 而后,将 .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) 复制代码
服务端渲染是指 Vue 在客户端将标签渲染成的整个 html 片断的工做在服务端完成,服务端造成的 html 片断直接返回给客户端这个过程就叫作服务端渲染。
更好的 SEO: 由于 SPA 页面的内容是经过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,因此在 SPA 中是抓取不到页面经过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),因此搜索引擎爬取工具能够抓取渲染好的页面;
更快的内容到达时间(首屏加载更快): SPA 会等待全部 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等须要必定的时间等,因此首屏渲染须要必定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,因此 SSR 有更快的内容到达时间;
更多的开发条件限制: 例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会致使一些外部扩展库须要特殊处理,才能在服务端渲染应用程序中运行;而且与能够部署在任何静态文件服务器上的彻底静态单页面应用程序 SPA 不一样,服务端渲染应用程序,须要处于 Node.js server 运行环境;
更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源,所以若是你预料在高流量环境下使用,请准备相应的服务器负载,并明智地采用缓存策略。
在 vue 项目中除了能够在 webpack.base.conf.js 中 url-loader 中设置 limit 大小来对图片处理,对小于 limit 的图片转化为 base64 格式,其他的不作操做。因此对有些较大的图片资源,在请求资源的时候,加载会很慢,咱们能够用 image-webpack-loader来压缩图片:
(1) 首先,安装 image-webpack-loader:
npm install image-webpack-loader --save-dev
复制代码
(2) 而后,在 webpack.base.conf.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, } } ] } 复制代码
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" ] 复制代码
若是项目中没有去将每一个页面的第三方库和公共模块提取出来,则项目会存在如下问题:
因此咱们须要将多个页面的公共代码抽离成单独的文件,来优化以上问题 。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'] }) 复制代码
当使用 DOM 内模板或 JavaScript 内的字符串模板时,模板会在运行时被编译为渲染函数。一般状况下这个过程已经足够快了,但对性能敏感的应用仍是最好避免这种用法。
预编译模板最简单的方式就是使用单文件组件——相关的构建设置会自动把预编译处理好,因此构建好的代码已经包含了编译出来的渲染函数而不是原始的模板字符串。
若是你使用 webpack,而且喜欢分离 JavaScript 和模板文件,你可使用 vue-template-loader,它也能够在构建过程当中把模板文件转换成为 JavaScript 渲染函数。
当使用单文件组件时,组件内的 CSS 会以 style 标签的方式经过 JavaScript 动态注入。这有一些小小的运行时开销,若是你使用服务端渲染,这会致使一段 “无样式内容闪烁 (fouc) ” 。将全部组件的 CSS 提取到同一个文件能够避免这个问题,也会让 CSS 更好地进行压缩和缓存。
查阅这个构建工具各自的文档来了解更多:
咱们在项目进行打包后,会将开发中的多个文件代码打包到一个文件中,而且通过压缩、去掉多余的空格、babel编译化后,最终将编译获得的代码会用于线上环境,那么这样处理后的代码和源代码会有很大的差异,当有 bug的时候,咱们只能定位到压缩处理后的代码位置,没法定位到开发环境中的代码,对于开发来讲很差调式定位问题,所以 sourceMap 出现了,它就是为了解决很差调式代码问题的。
SourceMap 的可选值以下(+ 号越多,表明速度越快,- 号越多,表明速度越慢, o 表明中等速度 )
缘由以下:
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, 由于它会增长文件的大小,可是在开发环境中,能够试用下,由于他们打包的速度很快。
Webpack 输出的代码可读性很是差并且文件很是大,让咱们很是头疼。为了更简单、直观地分析输出结果,社区中出现了许多可视化分析工具。这些工具以图形的方式将结果更直观地展现出来,让咱们快速了解问题所在。接下来说解咱们在 Vue 项目中用到的分析工具:webpack-bundle-analyzer 。
咱们在项目中 webpack.prod.conf.js 进行配置:
if (config.build.bundleAnalyzerReport) { var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; webpackConfig.plugins.push(new BundleAnalyzerPlugin()); } 复制代码
执行 $ npm run build --report 后生成分析报告以下:
gzip 是 GNUzip 的缩写,最先用于 UNIX 系统的文件压缩。HTTP 协议上的 gzip 编码是一种用来改进 web 应用程序性能的技术,web 服务器和客户端(浏览器)必须共同支持 gzip。目前主流的浏览器,Chrome,firefox,IE等都支持该协议。常见的服务器如 Apache,Nginx,IIS 一样支持,gzip 压缩效率很是高,一般能够达到 70% 的压缩率,也就是说,若是你的网页有 30K,压缩以后就变成了 9K 左右;
如下咱们以服务端使用咱们熟悉的 express 为例,开启 gzip 很是简单,相关步骤以下:
npm install compression --save
复制代码
var compression = require('compression'); var app = express(); app.use(compression()) 复制代码
Chrome 的 Performance 面板能够录制一段时间内的 js 执行细节及时间。使用 Chrome 开发者工具分析页面性能的步骤以下。
打开 Chrome 开发者工具,切换到 Performance 面板
点击 Record 开始录制
刷新页面或展开某个节点
点击 Stop 中止录制
本文经过如下三部分组成:Vue 代码层面的优化、webpack 配置层面的优化、基础的 Web 技术层面的优化;来介绍怎么去优化 Vue 项目的性能。 但愿对读完本文的你有帮助、有启发,若是有不足之处,欢迎批评指正交流!