共计 8387 个字符,预计需要花费 21 分钟才能阅读完成。
咱们在开发 Vue 我的项目时候都晓得,在 vue 开发中某些问题如果后期疏忽掉,过后不会呈现显著的成果,然而越向后开发越难做,而且我的项目做久了就会呈现问题,这就是所说的蝴蝶效应,这样前期的保护老本会十分高,并且我的项目上线后还会影响用户体验,也会呈现加载慢等一系列的性能问题,上面举一个简略的例子。
举个简略的例子
如果加载我的项目的时候加载一张图片须要 0.1s,其实算不了什么能够忽略不计,然而如果我有 20 张图片,这就是 2s 的工夫, 2s 的工夫不算长一下就过来了,然而这仅仅的只是加载了图片,还有咱们的 js,css 都须要加载,那就须要更长的工夫,可能是 5s,6s…,比方加载工夫是 5s, 用户可能等都不会等,间接敞开咱们的网站,最初导致咱们网站流量很少,流量少就没人用,没人用就没有钱,没有钱就涨不了工资,涨不了工资最初就是跑路了😂。通过下面的例子能够看出性能问题是如许的重要甚至关系到了咱们薪资😂, 那如何防止这些问题呢?废话不多说,上面分享一下本人在写我的项目的时用到的一些优化计划以及注意事项。
1. 不要将所有的数据都放在 data 中
能够将一些不被视图渲染的数据申明到实例内部而后在外部援用援用,因为 Vue2 初始化数据的时候会将 data 中的所有属性遍历通过 Object.definePrototype
从新定义所有属性;Vue3 是通过 Proxy 去对数据包装,外部也会波及到递归遍历,在属性比拟多的状况下很消耗性能
<template> | |
<button @click="updateValue">{{msg}}</button> | |
</template> | |
<script> | |
let keys=true; | |
export default { | |
name:'Vueinput', | |
data(){ | |
return {msg:'true'} | |
}, | |
created(){this.text = 'text'}, | |
methods:{updateValue(){ | |
keys = !keys | |
this.msg = keys?'true':'false' | |
} | |
} | |
} | |
</script> |
2.watch 尽量不要应用 deep:true 深层遍历
因为 watch 不存在缓存,是指定监听对象,如果 deep:true, 并且监听对象类型状况下,会递归解决收集依赖,最初触发更新回调
3. vue 在 v-for 时给每项元素绑定事件须要用事件代理
vue 源码中是通过 addEventLisener 去给 dom 绑定事件的,比方咱们应用 v -for 须要渲染 100 条数据并且并为每个节点增加点击事件,如果每个都绑定事件那就存在很多的 addEventLisener,这里不用说性能上必定不好,那咱们就须要应用事件代理解决这个问题
<template> | |
<ul @click="EventAgent"> | |
<li v-for="(item) in mArr" :key="item.id" :data-set="item">{{item.day}}</li> | |
</ul> | |
</template> | |
<script> | |
let keys=true; | |
export default { | |
name:'Vueinput', | |
data(){ | |
return { | |
mArr:[{ | |
day:1, | |
id:'xx1' | |
},{ | |
day:2, | |
id:'xx2' | |
},{ | |
day:2, | |
id:'xx2' | |
}, | |
... | |
] | |
} | |
}, | |
methods:{EventAgent(e){ | |
// 留神这里 在我的项目中千万不要写的这么简略,我只是为了不便了解才这么写的 | |
console.log(e.target.getAttribute('data-set')) | |
} | |
} | |
} | |
</script> |
4. v-for 尽量不要与 v -if 一起应用
vue 的编译过程是 template->vnode, 看上面的例子
// 假如 data 中存在一个 arr 数组 | |
<div id="container"> | |
<div v-for="(item,index) in arr" v-if="arr.length" key="item.id">{{item}}</div> | |
</div> |
下面的例子有可能大家常常这么做,其实这么做也能达到成果然而在性能下面不是很好,因为 Ast 在转化为 render 函数的时候会将每个遍历生成的对象都会退出 if 判断,最初在渲染的时候每次都每个遍历对象都会判断一次须要不须要渲染,这样就很节约性能,为了防止这个问题咱们把代码略微改一下
<div id="container" v-if="arr.length"> | |
<div v-for="(item,index) in arr" >{{item}}</div> | |
</div> |
这样就只判断一次就能达到渲染成果了,是不是更好一些那
参考 前端 vue 面试题具体解答
5. v-for 的 key 进行不要以遍历索引作为 key
<template> | |
<ul> | |
<li v-for="(item,index) in mArr" :key="item.id | |
<input type="checkbox" :value="item.is" /> | |
</li> | |
<button @click="remove"> | |
移除 | |
</button> | |
</ul> | |
</template> | |
<script> | |
let keys=true; | |
export default { | |
name:'Vueinput', | |
data(){ | |
return {mArr:[{is:false,id:1},{is:false,id:2} | |
] | |
} | |
}, | |
methods:{remove(e){console.log('asd') | |
this.mArr.shift()} | |
} | |
} | |
</script> |
调整一下代码
<template> | |
<ul> | |
<li v-for="(item,index) in mArr" :key="index"> | |
<input type="checkbox" :value="item.is" /> | |
</li> | |
<button @click="remove"> | |
移除 | |
</button> | |
</ul> | |
</template> | |
<script> | |
let keys=true; | |
export default { | |
name:'Vueinput', | |
data(){ | |
return {mArr:[{is:false,id:1},{is:false,id:2} | |
] | |
} | |
}, | |
methods:{remove(e){console.log('asd') | |
this.mArr.shift()} | |
} | |
} | |
</script> |
还是选中状态,就很神奇,解释一下为什么这么神奇,因为咱们选中的是 0 索引,而后点击移除后索引为 1 的就变为 0,Vue 的更新策略是复用 dom, 也就是说我索引为 1 的 dom 是用的之前索引为 0 的 dom 并没有更改,当然没有 key 的状况也是如此,所以 key 值必须为惟一标识才会做更改
6. SPA 页面采纳 keep-alive 缓存组件
<template> | |
<div> | |
<keep-alive> | |
<router-view></router-view> | |
</keep-alive> | |
</div> | |
</template> |
应用了 keep-alive 之后咱们页面不会卸载而是会缓存起来,keep-alive 底层应用的 LRU 算法(淘汰缓存策略),当咱们从其余页面回到初始页面的时候不会从新加载而是从缓存里获取,这样既缩小 Http 申请也不会耗费过多的加载工夫
7. 防止应用 v -html
- 可能会导致 xss 攻打
- v-html 更新的是元素的 innerHTML。内容按一般 HTML 插入,不会作为 Vue 模板进行编译。
8. 提取公共代码,提取组件的 CSS
将组件中公共的办法和 css 款式别离提取到各自的公共模块下,当咱们须要应用的时候在组件中应用就能够,大大减少了代码量
9. 首页白屏 -loading
当咱们第一次进入 Vue 我的项目的时候,会呈现白屏的状况,为了防止这种难堪的状况,咱们在 Vue 编译之前应用加载动画防止
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width,initial-scale=1.0"> | |
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> | |
<title>Vue</title> | |
<style> | |
</style> | |
</head> | |
<body> | |
<noscript> | |
<strong>We're sorry but production-line doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> | |
</noscript> | |
<div id="app"> | |
<div id="loading"> | |
loading | |
</div> | |
</div> | |
<!-- built files will be auto injected --> | |
</body> | |
</html> |
加 loading 只是解决白屏问题的一种,也能够缩短首屏加载工夫,就须要在其余方面做优化,这个能够参考前面的案例
10. 拆分组件
次要目标就是进步复用性、减少代码的可维护性, 缩小不必要的渲染,对于如何写出高性能的组件这里就不展现了,本人能够多看看那些比拟火的 UI 库(Element,Antd)的源码
11. 正当应用 v-if 当值为 false 时外部指令不会执行, 具备阻断性能
如果操作不是很频繁能够应用 v -if 代替 v -show,如果很频繁咱们能够应用 v -show 来解决
key 保障唯一性 (默认 vue 会采纳就地复用策略)
下面的第五条曾经讲过了,如果 key 不是惟一的状况下,视图可能不会更新。
12. 获取 dom 应用 ref 代替 document.getElementsByClassName
mounted(){console.log(document.getElementsByClassName(“app”)) | |
console.log(this.$refs['app']) | |
} |
document.getElementsByClassName 获取 dom 节点的作用是一样的,但应用 ref 会缩小获取 dom 节点的耗费
13. Object.freeze 解冻数据
首先说一下
Object.freeze
的作用
- 不能增加新属性
- 不能删除已有属性
- 不能批改已有属性的值
- 不能批改原型
- 不能批改已有属性的可枚举性、可配置性、可写性
data(){ | |
return:{ | |
objs:{name:'aaa'} | |
} | |
}, | |
mounted(){this.objs = Object.freeze({name:'bbb'}) | |
} |
应用 Object.freeze 解决的 data 属性,不会被 getter,setter,缩小一部分耗费,然而
Object.freeze
也不能滥用,当咱们须要一个十分长的字符串的时候举荐应用
14. 正当应用路由懒加载、异步组件
当打包构建利用时,JavaScript 包会变得十分大,影响页面加载。而后当路由被拜访的时候才加载对应组件,这样就更加高效了
// 未应用懒加载的路由 | |
import Vue from "vue"; | |
import VueRouter from "vue-router"; | |
import Home from "@/views/Home"; | |
import About from "@/views/About"; | |
Vue.use(VueRouter); | |
const routes = [ | |
{ | |
path: "/", | |
name: "Home", | |
component: Home, | |
}, | |
{ | |
path: "/about", | |
name: "About", | |
component: About | |
} | |
]; | |
// 应用懒加载 | |
import Vue from "vue"; | |
import VueRouter from "vue-router"; | |
Vue.use(VueRouter); | |
const Home = () => import('../views/Home') | |
const About = () => import('../views/About') | |
const routes = [ | |
{ | |
path: "/", | |
name: "Home", | |
component: Home, | |
}, | |
{ | |
path: "/about", | |
name: "About", | |
component: About | |
} | |
]; |
15. 数据长久化的问题
数据长久化比拟常见的就是 token 了,作为用户的标识也作为登录的状态,咱们须要将其贮存到
localStorage
或sessionStorage
起来每次刷新页面 Vuex 从localStorage
或sessionStorage
获取状态,不然每次刷新页面用户都须要从新登录,从新获取数据
- localStorage 须要用户手动移除能力移除,不然永恒存在。
- sessionStorage 敞开浏览器窗口就生效。
- cookie 敞开浏览器窗口就生效, 每次申请 Cookie 都会被一起提交给服务器。
16. 防抖、节流
这两个算是陈词滥调了,就不演示代码了,上面介绍一个场景,比方咱们注册新用户的时候用户输出昵称须要校验昵称的合法性,思考到用户输出的比拟快或者批改频繁,这时候咱们须要应用节流,间隔性的去校验,这样就缩小了判断的次数达到优化的成果。前面咱们还须要须要用户手动点击保留能力注册胜利,为了防止用户频繁点击保留并发送申请,咱们只监听用户最初一次的点击,这时候就用到了节流操作,这样就能达到优化成果
17. 重绘,回流
-
触发重绘
浏览器从新渲染局部或者全副文档的过程叫回流
- 频繁操作元素的款式,对于动态页面,批改类名,款式
- 应用可能触发重绘的属性(background,visibility,width,height,display 等)
-
触发回流
浏览器回将新款式赋予给元素这个过程叫做重绘
- 增加或者删除节点
- 页面首页渲染
- 浏览器的窗口发生变化
- 内容变换
回流的性能耗费比重绘大, 回流肯定会触发重绘,重绘不肯定会回流;回流会导致渲染树须要从新计算,开销比重绘大,所以咱们要尽量避免回流的产生.
18. vue 中的 destroyed
组件销毁时候须要做的事件,比方当页面卸载的时候须要将页面中定时器革除,销毁绑定的监听事件
19. vue3 中的异步组件
异步组件与上面的组件懒加载原理是相似,都是须要应用了再去加载
<template> | |
<logo-img /> | |
<hello-world msg="Welcome to Your Vue.js App" /> | |
</template> | |
<script setup> | |
import {defineAsyncComponent} from 'vue' | |
import LogoImg from './components/LogoImg.vue' | |
// 简略用法 | |
const HelloWorld = defineAsyncComponent(() => | |
import('./components/HelloWorld.vue'), | |
) | |
</script> |
20. 组件懒加载
<template> | |
<div id="content"> | |
<div> | |
<component v-bind:is='page'></component> | |
</div> | |
| |
</div> | |
</template> | |
<script> | |
// ---* 1 应用标签属性 is 和 import *--- | |
const FirstComFirst = ()=>import("./FirstComFirst") | |
const FirstComSecond = ()=>import("./FirstComSecond") | |
const FirstComThird = ()=>import("./FirstComThird") | |
export default { | |
name: 'home', | |
components: { }, | |
data: function(){ | |
return{page: FirstComFirst} | |
} | |
} | |
</script> |
原理与路由懒加载一样的,只有须要的时候才会加载组件
21. 动静图片应用懒加载,动态图片应用精灵图
- 动静图片参考图片懒加载插件
- 动态图片,将多张图片放到一起,加载的时候节省时间
22. 第三方插件的按需引入
element-ui 采纳 babel-plugin-component插件来实现按需导入
// 装置插件 | |
npm install babel-plugin-component -D | |
// 批改 babel 文件 | |
module.exports = {presets: [['@babel/preset-env', { modules: false}], '@vue/cli-plugin-babel/preset'], | |
plugins: [ | |
'@babel/plugin-proposal-optional-chaining', | |
'lodash', | |
[ | |
'component', | |
{ | |
libraryName: 'element-ui', | |
styleLibraryName: 'theme-chalk' | |
}, | |
'element-ui' | |
], | |
[ | |
'component', | |
{ | |
libraryName: '@xxxx', | |
camel2Dash: false | |
}, | |
] | |
] | |
}; |
23. 第三方库 CDN 减速
//vue.config.js | |
let cdn = {css: [], js: []}; | |
// 辨别环境 | |
const isDev = process.env.NODE_ENV === 'development'; | |
let externals = {}; | |
if (!isDev) { | |
externals = { | |
'vue': 'Vue', | |
'vue-router': 'VueRouter', | |
'ant-design-vue': 'antd', | |
} | |
cdn = { | |
css: ['https://cdn.jsdelivr.net/npm/[email protected]/dist/antd.min.css', // 提前引入 ant design vue 款式 | |
], // 搁置 css 文件目录 | |
js: ['https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js', // vuejs | |
'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.min.js', | |
'https://cdn.jsdelivr.net/npm/[email protected]/dist/antd.min.js' | |
] | |
} | |
} | |
module.exports = { | |
configureWebpack: { | |
// 排除打包的某些选项 | |
externals: externals | |
}, | |
chainWebpack: config => { | |
// 注入 cdn 的变量到 index.html 中 | |
config.plugin('html').tap((arg) => {arg[0].cdn = cdn | |
return arg | |
}) | |
}, | |
} | |
//index.html | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width,initial-scale=1.0"> | |
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> | |
<title><%= htmlWebpackPlugin.options.title %></title> | |
<!-- 引入 css-cdn 的文件 --> | |
<% for(var css of htmlWebpackPlugin.options.cdn.css) { %> | |
<link rel="stylesheet" href="<%=css%>"> | |
<% } %> | |
</head> | |
<body> | |
<noscript> | |
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> | |
</noscript> | |
<!-- 搁置 js-cdn 文件 --> | |
<% for(var js of htmlWebpackPlugin.options.cdn.js) { %> | |
<script src="<%=js%>" ></script> | |
<% } %> | |
<div id="app"></div> | |
</body> | |
</html> |
最初
以上的优化计划不紧在代码层面起到优化而且在性能上也起到了优化作用,文章内容次要是从 Vue 开发的角度和局部通过源码的角度去总结的,文章中如果存在谬误的中央,或者你认为还有其余更好的计划,请大佬在评论区中指出,作者会及时更正,感激!