### 一、代码层面的优化
封装公用组件达到复用目的
减少http请求次数
合理运用缓存
**1.1、v-if 和 v-show 区分使用场景**
v-if 是 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show 就简单得多, 不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 display 属性进行切换。
所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。
**1.2、computed 和 watch 区分使用场景**
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
运用场景:
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
**1.3、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="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>
```
**1.4、长列表性能优化**
Vue 会通过 Object.defineProperty 对数据进行劫持,来实现视图响应数据的变化,然而有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要 Vue 来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,那如何禁止 Vue 劫持我们的数据呢?可以通过 Object.freeze 方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。
```
export default {
data: () => ({
users: {}
}),
async created() {
const users = await axios.get("/api/users");
this.users = Object.freeze(users);
}
};
```
Object.freeze()方法接收一个参数,如果此参数是一个对象,则此方法把这个对象**冻结,**如果是其他类型则不会报错,无影响。
**被冻结的对象不能修改、添加、删除其属性或者属性值**
一个大的数据对象里,在你确信它不需要改变的时候,你可以给他freeze(),可以大大的增加性能。
### **1.5、事件的销毁**
Vue 组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。也就是说,在js内使用addEventListener等方式是不会自动销毁的,我们需要在组件销毁时手动移除这些事件的监听,以免造成内存泄露
```
created() {
addEventListener('click', this.click, false)
},
beforeDestroy() {
removeEventListener('click', this.click, false)
}
```
**JS的回收机制**
JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收系统(GC)会按照固定的时间间隔,周期性的执行。
到底哪个变量是没有用的?所以垃圾收集器必须跟踪到底哪个变量没用,对于不再有用的变量打上标记,以备将来收回其内存。用于标记的无用变量的策略可能因实现而有所区别,通常情况下有两种实现方式:**标记清除**和**引用计数**。引用计数不太常用,标记清除较为常用。
2、标记清除
js中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。
```
function test(){
var a=10;//被标记,进入环境
var b=20;//被标记,进入环境
}
test();//执行完毕之后a、b又被标记离开环境,被回收
```
一、什么是内存泄露
内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。在C++中,因为是手动管理内存,内存泄露是经常出现的事情。而现在流行的C#和Java等语言采用了自动垃圾回收方法管理内存,正常使用的情况下几乎不会发生内存泄露。浏览器中也是采用自动垃圾回收方法管理内存,但由于浏览器垃圾回收方法有bug,会产生内存泄露。
二、内存泄露的几种情况
1、当页面中元素被移除或替换时,若元素绑定的事件仍没被移除,在IE中不会作出恰当处理,此时要先手工移除事件,不然会存在内存泄露。
```
<div id="myDiv">
<input type="button" value="Click me" id="myBtn">
</div>
<script type="text/javascript">
var btn = document.getElementById("myBtn");
btn.onclick = function(){
document.getElementById("myDiv").innerHTML = "Processing...";
}
</script>
```
应改成下面
```
<div id="myDiv">
<input type="button" value="Click me" id="myBtn">
</div>
<script type="text/javascript">
var btn = document.getElementById("myBtn");
btn.onclick = function(){
btn.onclick = null;
document.getElementById("myDiv").innerHTML = "Processing...";
}
</script>
```
或者采用事件委托
```
<div id="myDiv">
<input type="button" value="Click me" id="myBtn">
</div>
<script type="text/javascript">
document.onclick = function(event){
event = event || window.event;
if(event.target.id == "myBtn"){
document.getElementById("myDiv").innerHTML = "Processing...";
}
}
</script>
```
2、意外的全局变量引起的内存泄露
```
function leak(){
leak="xxx";//leak成为一个全局变量,不会被回收
}
```
3、闭包引起的内存泄露
```
function bindEvent(){
var obj=document.createElement("XXX");
obj.οnclick=function(){
//Even if it's a empty function
}
}
```
闭包可以维持函数内局部变量,使其得不到释放。 上例定义事件回调时,由于是函数内定义函数,并且内部函数--事件回调的引用外暴了,形成了闭包。
解决之道,将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。
```
//将事件处理函数定义在外部
function onclickHandler(){
//do something
}
function bindEvent(){
var obj=document.createElement("XXX");
obj.οnclick=onclickHandler;
}
```
```
//在定义事件处理函数的外部函数中,删除对dom的引用
function bindEvent(){
var obj=document.createElement("XXX");
obj.οnclick=function(){
//Even if it's a empty function
}
obj=null;
}
```
4、没有清理的DOM元素引用
5、被遗忘的定时器或者回调
**1.6、图片资源懒加载**
对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的提升,也提高了用户体验。我们在项目中使用 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">
```
以上为 vue-lazyload 插件的简单使用
**1.7、路由懒加载**
Vue 是单页面应用,可能会有很多的路由引入 ,这样使用 webpcak 打包后的文件很大,当进入首页时,加载的资源过多,页面会出现白屏的情况,不利于用户体验。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了。这样会大大提高首屏显示的速度,但是可能其他的页面的速度就会降下来。
**路由懒加载:**
```
const Foo = () => import('./Foo.vue')
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
```
**异步组件**
如果组件在页面加载时不需要,只在调用时用到,这时可以使用异步组件的写法。仅仅是引入和组件注册写法不同
```
// template
<test v-if="showTest"></test>
// script
components: {
test: () => import('./test') // 将组件异步引入,告诉webpack,将该部分代码分割打包
},
methods:{
clickTest () {
this.showTest = !this.showTest
}
}
```
不需要首屏加载的组件都使用异步组件的方式来加载(如多tab),包括需要触发条件的动作也使用异步组件(如弹窗)
使用方式为:v-if来控制显示时机,引入组件的Promise即可
```
<template>
<div>
<HellowWorld v-if="showHello" />
</div>
</template>
<script>
export default {
components: { HellowWorld: () => import('../components/HelloWorld.vue') },
data() {
return {
showHello: false
}
},
methods: {
initAsync() {
addEventListener('scroll', (e) => {
if (scrollY > 100) {
this.showHello = true
}
})
}
}
}
</script>
```
###### 1.8、第三方插件的按需引入
我们在项目中经常会需要引入第三方插件,如果我们直接引入整个插件,会导致项目的体积太大,我们可以借助 `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)
```
然后引入部分组件,这样一来,就不需要引入样式了,插件会帮我们处理。
**mint-ui**
由于mint-ui是element-ui的移动端组件,所以它的使用和引入几乎和element-ui一样。
首先,安装 babel-plugin-component:
```
npm install babel-plugin-component -D
```
然后,将.babelrc 修改为:
```
{
"presets": [
["es2015", { "modules": false }]
],
"plugins": [["component", [
{
"libraryName": "mint-ui",
"style": true
}
]]]
}
```
然后引入部分组件
```
// main.js
import Vue from 'vue'
import { Toast, MessageBox } from 'element-ui'
Vue.use(Dialog)
Vue.use(Loading.directive)
Vue.prototype.$loading = Loading.service
// 然后正常使用组件
```
### 1.9、优化无限列表性能
1. 安装 vue-virtual-scroll-list
```
npm install vue-virtual-scroll-list --save
```
2. 引入下载好的组件 记得局部注册一下组件
```
import virtualList from 'vue-virtual-scroll-list'
```
3. v-for循环的方式 循环数据
```
<template>
<div class="about">
<h1>This is an about page</h1>
<div>
<virtual-list :size="40" :remain="8">
<div v-for="item of items" :key="item.id">{{ item }}</div>
</virtual-list>
</div>
</div>
</template>
<script>
import virtualList from 'vue-virtual-scroll-list'
export default {
data() {
return {
items: []
}
},
methods: {
forss() {
for (let i = 1; i <= 1000000; i++) {
this.items.push({
id: i,
name:
i +
'12123d5a45ad56addadjak放假啊就恢复健康发号放假安徽科技发货方就按客户'
})
}
}
},
created() {
this.forss()
},
components: { 'virtual-list': virtualList }
}
</script>
<style lang="scss" scoped></style>
```
**1.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 SSR 如何实现,可以参考作者的另一篇文章《Vue SSR 踩坑之旅》。如果你的 Vue 项目只需改善少数营销页面(例如 /, /about, /contact 等)的 SEO,那么你可能需要预渲染,在构建时 (build time) 简单地生成针对特定路由的静态 HTML 文件。优点是设置预渲染更简单,并可以将你的前端作为一个完全静态的站点,具体你可以使用 prerender-spa-plugin 就可以轻松地添加预渲染 。
### **二 打包优化**
**1、修改vue.config.js中的配置项**,把productionSourceMap设置为false,不然最终打包过后会生成一些map文件,如果不关掉,生产环境是可以通过map去查看源码的,并且可以开启gzip压缩,使打包过后体积变小。
![img](https://pics2.baidu.com/feed/562c11dfa9ec8a132a68fdb8c1279d89a0ecc026.jpeg?token=d912967f270d040e815637154cc31570&s=D29032C19AA7916C4471948D02007081)
**2、使用cdn的方式外部加载一些资源**
vue打包时会默认将第三方包的所有css、js文件打包到vendor.js中,所以我们需要将部分大的文件剥离开,使用CDN资源的形式引用比如vue-router、axios等Vue的周边插件,在webpack.config.js里面,externals里面设置一些不必要打包的外部引用模块。然后在入门文件index.html里面通过cdn的方式去引入需要的插件。
![img](https://pics1.baidu.com/feed/bba1cd11728b4710aa21bb3cf6eacffbfd0323d9.jpeg?token=e840f97e0dd2a72c306eb4c1a06dfdbb&s=52B03AC09AA6A76E4A54E0050000E0C3)
![img](https://pics0.baidu.com/feed/b90e7bec54e736d164224a68ac7443c4d5626943.jpeg?token=e2cbf198e7d3a7070521ffff4b5bd807&s=92923EC49AEE856C4654E0070200E0C3)
**3、减少图片使用**,因为对于网页来说,图片会占用很大一部分体积,所以,优化图片的操作可以有效的来加快加载速度。可以用一些css3的效果来代替图片效果,或者使用雪碧图来减少图片的体积。
**4、按需引入**,咱们使用的一些第三方库可以通过按需引入的方式加载。避免引入不需要使用的部分,无端增加项目体积。比如在使用element-ui库的时候,可以只引入需要用到的组件。