vue MVVM双向数据绑定绑定原理。
- M指的是model,模型;V指的是view,视图;VM指的是viewModel,视图模型。model指的是js中的数据,model通过数据绑定实现视图的更新,而视图改变模型是通过DOM事件监听。
vue MVVM的原理:
- 观察者-订阅者(数据劫持):vue中Observer数据监听器,把一个普通的对象传给Vue实例的data选项,vue将把这个对象的所有属性进行遍历,并使用Object.defineProperty()方法将这些属性全部转为setter和getter方法。当data中的属性发生改变时,则会调用setter方法,访问某个属性时则会调用getter方法。Complie指令解析器,它的作用是对每个元素节点的指令进行解析(解析小胡子里的变量或者是js语句),替换模板数据。初始化视图,并订阅Watcher来更新视图。此时watcher(收集订阅者)会将自己添加到消息订阅器Dep(消息订阅器)中初始化完毕。当数据发生变化时,Observer中的setter会触发,setter会立即调用Dep.notify()函数,Dep开始遍历所有的订阅者,并调用遍历者的update方法,订阅者收到通知后对视图进行响应的改变。
- Object.defineproperty()方法做的数据绑定,而这个又无法通过兼容性处理,所以Vue2.0不支持IE8以下的浏览器。Vue3.0使用了Proxy代理方法,它的作用就是遍历data中的属性,把它代理到vm的实例上,。
- Proxy对比于Object.defineProperty()的优点
- Proxy可以直接监听对象而非属性
- Proxy可以直接监听数组的变化
- Proxy有13中拦截方式
- Proxy返回的是一个新对象,我们可以只操作新对象达到目的,而Object.defineProperty只能遍历对象属性直接修改。
二、vue的生命周期
- beforeCreate:实例组件刚被创建,DOM和数据data还没有初始化。当前阶段data、watch、Dom、computed、methods上的数据都不可以访问。
- created:数据data已经初始化,方法也已经可以调用,但是DOM未渲染。在这个周期里可以进行请求改变数据并进行渲染。 当前阶段呢无法于dom进行交互。
- 请求接口一般放在created;虽说在created、beforeMount、mounted三个钩子函数中,data已经创建,但是推荐在created中请求接口;因为在created中有以下优点
- 能更快获取到服务器的数据,减少页面加载时间。
- ssr不支持beforeMount和mounted钩子函数,放在created有助于一致性。
- 请求接口一般放在created;虽说在created、beforeMount、mounted三个钩子函数中,data已经创建,但是推荐在created中请求接口;因为在created中有以下优点
- beforeMount:DOM未完成挂载,data数据已经初始化完成,但是数据的双向绑定还是显示{{}},这是因为Vue采用了虚拟DOM,先占住一个坑。
- mounted:数据和DOM都完成了挂载,在上一个周期占位的数据把值给渲染进去。一般请求会放在这个地方;因为这里请求改变数据之后刚还能渲染。当前阶段真是的Dom完成挂载;数据完成双向绑定,可以访问到Dom节点。
- beforeUpdate:只要是页面数据改变都会触发,数据更新之前,页面的数据还是原来的数据
- updated:只要是页面数据改变就会触发,数据更新完毕,页面的数据是更新完成后的。
- beforeDestory:组件销毁之前执行。
- destoryed:组件销毁之后执行。
父子组件生命周期钩子函数的执行顺序
- 加载渲染过程:父组件beforeCreate=>父组件created=>父组件beforeMount=>子组件beforeCreate=>子组件Created=>子组件beforeMount=>子组件mounted=>父组件mounted
- 子组件的更新过程:父组件beforeUpdate=>子组件beforeUpdate=>子组件updated=>父组件updated
- 父组件更新过程:父组件beforeUpdate=>父组件updated
- 销毁过程:父组件beforeDestroy=>子组件beforeDestroy=>子组件destroyed=>父组件destroyed
vue中class和style绑定
绑定类名方法
-
对象语法
<div :class="{active:isActive,text:isHover}"></div> //当然类名也可以利用计算属性进行设置 <div :class="classObject"></div> data(){ return { isActive:false, isHover:true } }, computed:{ classObject(){ return { active:this.isActive, text:this.isHover } } } 复制代码
- class中的类名active和text是否存在取决于isActive和isHover是否是true,如上最终的编译结果是
<div class = "text"></div> 复制代码
-
数组语法
<div :class="[isActive?'active':'',text]"></div> <!-- 如果三元判断较为繁琐,也可以数组结合对象 --> <div :class="[{active:isActive},text]"></div> 复制代码
绑定style 内联式
- 对象语法
<div :style="{color:'red',font-size:'16px'}"></div>
复制代码
- 数组语法
<div :style="[baseStyle,baseColor]"></div>
data(){
return{
baseStyle:{
font-size:'16px',
line-height:1,
},
baseColor:{
color:'red'
}
}
}
复制代码
- 多重值时,用对象和数组实现
<div :style="{display:['-webkit-box', '-ms-flexbox', 'flex']]"></div> 复制代码
vue中的weabpack随手笔记
在src同级的目录下创建一个vue.config.js文件
module.exports = {
publicPath:"/baidu", //根路径
outputDir:"dist", //构建输出目录
assetsDir:"assets", //存放静态资源;js、css、img等等
lintOnSave:true, //是否开启eslint保存检测;有效值:true,false,error
devServer:{
open:true, //启动项目默认浏览器自动打开项目
host:"0.0.0.0", //主机名字
port:"8080", //端口号
//proxy反向代理的原理是,客户端发送请求,webpack将请求代理到服务器上,webpack通过devServer将响应数据返回客户端
//常见的正向代理是vpn,其他的一般都是反向代理,代理的服务器如果帮助的是客户端则为正向代理,
//如果是帮助服务器端的则为反向代理
proxy:{
// 配置proxy跨域
"/api":{
target:"http://localhost:5000", //需要跨域的地址
pathRewirte:{
"^/api":"/api"
},
},
},
}
}
复制代码
vue脚手架内置文件介绍
- public:主要是放静态资源的文件,例如图片,全局css,引入的js脚本。
- package.json:配置文件
- src:主要是写咱们开发的源码
- assets:放静态资源,简易用来放图标,这个的图片可以选择编码转成base64。
- compoents:用来放公共组件的,复用性比较强。
- views:放页面级别的组件,复用性比较差。
- store:vuex 用来存放公共的数据。
- router:用来放路由配置的。
- main.js:主模块。
- app.vue:主组件。
vue中路由配置
- 首先利用npm安装vue-router;命令是npm i vue-router
- 在src目录下创建一个router文件,在文件下创建一个index.js文件;
import Vue from 'vue';
import VueRouter from 'vue-Router';
Vue.use(VueRouter)
const routes = [
{
path:'/home',
name:'home',
//路由懒加载;就是要展示这个组件时才加载这个组件的资源,可以减少首屏加载时间(性能优化)
component:()=>import('../views/home')
//嵌套路由也就是子路由配置
chlidren:[
{
path:'/home/son',
name:'son',
component:()=>import('../views/son')
}
],
//路由404,也就是路由重定向;这个需要注意只能放在最后,用来重定向的。
{
path:'*',
redirect:'/home',
}
}
];
const router = new VueRouter({
mode:'history',
routes,
});
export default router;
复制代码
- 在main.js中引入router.js文件
import router from './router'
const vm = new Vue({
router,
store,
render:h(APP)
}).$mount('#app');
复制代码
- 基本的路由配置就完成了。
- 如果再深入一点的路由配置就是设置黑白名单,也就是路由拦截。
router.beforEach((to,from,next)=>{
const noPassUrl = [];
if(noPassUrl.indexOf(to.path)>-1){
if(token){
next()
}else{
next({path:'/login'})
}
}
next()
})
复制代码
vue-router传参的形式有哪些,有什么区别
vue-router的传参形式分为两大类
router.push和<router-link></router-link>
- 编程式导航router.push,该路由跳转有两个传参形式;1是字符串;2是对象
- this.$router.push('home'),字符串的方式是直接将路由地址以字符串的方式进行跳转,这种方式很简单但是不能传递参数
- 对象的形式,分为两个方式
//传参 this.$router.push({name:'home',params:{useId:123}}) //接收参数 this.$route.params.useId //传参 this.$router.push({path:"/home",query:{use:'hello'}}) //接收参数 this.route.query.use 复制代码
- router-link传参
//传参 <router-link :to={name:'home',query:{id:25}}"></router-link> //接收 console.log(this.route.query.id) 复制代码
接收参数也可以换高级写法
-
路由传参的高级写法
//传参 this.$router.push({name:"home",query:{user:"张三"}}) //在路由中写 { path:'home', name:'home', props:route => ({user:route.query.user}) } //在接收的组件中利用props接收 props:{ user:{ type:String, default:() => '' } } 复制代码
-
params和query传参的区别
- params这种传递参数的形式,如果在目标页面刷新,传递的参数会丢失。
- query传参,参数会附在路由地址后面,在目标页刷新,数据不会丢失。
axios和ajax的区别,axios的基本配置。
- axios和ajax的区别:
- axios是基于promise的HTTP库,axja不支持promise,如果多个请求之间有先后顺序的话就会出现回调地狱。
- axios的基本配置。
- 先利用npm安装axios,命令npm i axios;
- 在src目录下创建一个request文件,在次文件下创建index.js,引入并配置。
import axios from 'axios' const service = axios.create({ timeout:6000; //设置请求超时时间。 //设置请求头传参类型 headers:{ "Content-Type": "application/x-www-form-urlencoded "}, baseURL:'',//这是请求地址的基础路径,将自动加载url前面 transformRequest:[function(data){ // 对data(传参)进行任意处理 //post请求传参为formdata时处理参数 const d = qs.stringify(data); return d; }], withCredentials:true, //默认值为false,表示跨域请求时是否需要凭证。配置cookie }) //请求拦截 axios.interceptors.request.use(config=>{ //请求之前做什么 if(token){ config.headers.Authorization = token } return config },error=>{ return Promise.reject(error) } ) //请求响应拦截 axios.interceptors.response.use( response=>{ //对响应数据做什么 return response }, error=>{ if(error.response.state==401){ //这里写清除token return Promise.rejece(error) } } ) //移除请求拦截器 var myInterceptors = axios.interceptors.request.use(config=>{ return config }) axios.interceptors.request.eject(myInterceptors) export default service 复制代码
vue中组件的数据的传递
父子组件之间的传递
- 父传子;在父级组件中自定义属性,子组件通过props接收参数。
// 子组件代码Son.vue
<template>
<div class="header">
<div class="logo">{{logo}}</div>
</div>
</template>
<script>
export default {
name:"Son",
data(){
return {
logo:"",//组要从父级组件中获取logo
}
},
props:['logo'], //接收父组件传过来的logo //其中props支持数组和对象的形式
props:{
// 自定义验证函数
propF: { validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1 },
propA:{
type:Number,
default:2
//默认值如果是个引用数据类型,必须写法如下
default:()=>{
a:1
}
}
}
}
</script>
// 父组件Parent.vue
<template>
<div class= "main">
<Son :logo = logoMsg></Son>
</div>
</template>
import Son from './components/Son'
<script>
export defalut {
name:"parent",
data(){
return {
logoMsg:"图片",
},
},
components:{
Son
}
}
</script>
复制代码
子传父:子组件通过触发自身函数,利用this.$emit触发父级组件中的自定义事件并传参。
// 子组件Son.vue
<template>
<div class="son">
<lable>
<input v-model= "userName" @change="setUser" />
</lable>
</div>
</template>
<script>
export default {
name:"son",
data(){
return{
userName:"",
}
},
methods:{
setUser(){
this.$emit("user",this.userName)
}
}
}
</sript>
//父组件parent.vue
<template>
<div class='main'>
<Son @uer="getUser"></Son>
<p>用户名{{userName}}</p>
</div>
</template>
import Son from "./components/Son"
<script>
export default {
name:"parent",
data(){
return{
userName:"",
}
},
components:{
Son
},
methods:{
getUser(name){
this.userName = name
}
}
}
</script>
复制代码
兄弟间的数据传递VueX
- 在src文件夹下创建一个store文件,然后在store文件下创建一个index.js文件
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
//state 存储的是store中变量,state的变量官方不建议直接修改,而是通过mutation中的方法进行修改
state:{
age:"",
name:"",
},
// mutations存放的是修改state中变量的方法,只支持同步的方法。通过this.$store.commit("setUrl")
触发,如果传参只是一个可以直接传,如果想要多个传参则需要已对象的形式进行传参。
例如:this.$store.commit('setUrl',{age:15,name:'hello'})
mutations:{
setUrl(state,data){
state.age = data.age,
state.name = data.name
}
},
// actions可以存放一些公共函数,它支持同步和异步的函数。也可以异步修改state的值,
原理是通过action里的方法触发mutations里的方法,进一步去改变state里的变量。
通过this.$store.dispatch('getName'),传递多个参数时。与mutations同理。
actions:{
getName(name){
console.log(name)
},//存放公共方法
// 异步修改state中的变量
setAge({commit},data){
commit("setUrl",data)
}
},
// getters 可以对state中的变量进行加工后传递外界。getters有两个参数。
第一个参数时state,第二个参数时getters,当前getters对象用于这个对象下的getters使用
通过this.$store.getters(fullInf)调用。
getters:{
nameInf(state){
return "姓名:"+ state.name
},
fullInf(state,getters){
return getters nameInf + '年龄:'+state.age
}
}
})
复制代码
$ref实现通信-父传子 子传父
- ref如果用在子组件上,通过$refs可以获取到子组件上的方法和属性。如果不是只是在普通的dom元素上使用,有选择器的作用。
//子组件
<template>
<div class="child">
{{msg}}
</div>
</template>
<script>
export default {
data(){
return{
msg:''
}
},
created(){
console.log(this.$parent.name) //获取父组件的name属性
},
methods:{
getMsg(m){
this.msg = m
}
}
}
</script>
//父组件
<template>
<div class="parent">
<Child ref='son'></Child>
<div>{{name}}</div>
</div>
</template>
import Child from './Child'
<script>
export default {
data(){
return {
name:'父组件',
}
},
mounted(){
this.$refs.son.getMsg('我是子组件')
}
}
</script>
复制代码
vue常用指令、相似功能指令间的区别以及注意事项
vue中的常用指令
- v-html:用于解析html代码并展示
<template>
<div v-html=msg></div>
</template>
<script>
export default {
data(){
return{
msg:'<h1>vue的v-html指令</h1>'
}
}
}
</script>
复制代码
- v-text:用于输出纯文本
- v-if:条件判断指令,值是ture是展示。
- v-show:显示隐藏dom元素。
- v-for:
- 数组迭代,最好在后面加上:key
<template> <div v-for="(item,index) in ary :key=index" ></div> </template> <script> export default { data(){ return{ ary:[1,2,3,4] } } } </script> 复制代码
- v-for循环对象
<ul> <li v-for="(value,name,index) in object" :key="name"> val:{{value}} key:{{name}} </li> </ul> //输出val:标题,key:title //val:年龄,key:age //val:名字,key:name //index 为索引 data(){ return{ object:{ title: "标题", age: "年龄", name: "名字", } } } 复制代码
- v-for循环数字
<ul> <li v-for="item in 10" :key="name"> {{item}} </li> </ul> //li会被循环三次 复制代码
- v-bind:绑定属性的指令,一般简写为:
- v-on:绑定事件的指令,一般简写为@
- v-model:实现表单数据和data数据双向绑定。
vue指令的区别和注意事项。
- v-if和v-show的区别:
- v-if和v-show都有着显示隐藏的作用;v-if是创建或者删除dom元素;v-show是在该元素的css上添加display:none/true;dom元素一直都在。频繁切换的时候使用v-show更加合适。
- v-for使用时需要的注意事项。
- 使用时最好在后面添加:key属性。相当于给组件加了一个唯一标识。key的作用:让diff操作更准确、更迅速。
- v-for和v-if同时使用;因为v-for的优先级比v-if高;这意味着v-if将重复与每个循环的元素中;非常影响性能。
永远不要把v-for和v-if同时用在一个元素上
- 如果我们在同一个元素上同时使用时,Vue是如何处理的
<ul> <li v-for="user in users" v-if="user.isActive" :key="user.id"> {{ user.name }} </li> </ul> //因为v-for的优先级比v-if高,所以会进行以下的运算 this.users.map(user=>{ if(user.isActive){ return user.name } }) /**当我们只需要渲染部分元素时,也会直接将整个this.users循环,非常影响性能 我们可以将v-if放到ui标签上或者使用cpmputed计算属性。*/ 复制代码
- 使用计算属性过滤v-if的条件
<ul>
<li v-for="user in activeUsers" :key='user.id'>
{{user.name}}
</li>
</ul>
computed:{
activeUsers(){
return this.users.filter(user=>{
return user.isActive
})
}
}
复制代码
用key来管理可复用的元素
- Vue会尽可能高效的渲染元素,通常会复用已有的元素而不是从头开始渲染。这么做除了使Vue变得非常快之外,还有一些好处。例如你允许用户使用不同的方式登录时;
<template v-if="loginType=='userName'"> <label>用户名</label> <input placeholder="请输入用户名" /> </template> <template v-else> <label>邮箱</label> <input placeholder="请输入邮箱号" /> </template> 复制代码
- 上面的代码切换loginType时,将不会清除用户已经输入的内容,因为两个模板使用了相同的元素,label元素不会被替换掉,他只会将邮箱替换掉用户名,input元素也是,仅仅是替换掉placeholder属性,而input框的输入内容不会变。因此如果我们想要这两个input元素是独立的,只需要添加唯一值key。如下,这样切换登录方式时,input框是两个独立的元素,输入框的内容只在当前状态下生效,互不影响。
<template v-if="loginType=='userName'"> <label>用户名</label> <input placeholder="请输入用户名" key='userName' /> </template> <template v-else> <label>邮箱</label> <input placeholder="请输入邮箱号" key='email' /> </template> 复制代码
vue常用的事件修饰符
v-model的常用修饰符
- .lazy 等待input失去光标的时候,在去改变v-model的值以及change事件也是。
<input type="text" v-model.lazy="int" /> 复制代码
- .number 自动将用户输入的非数字的值
<input type="text" v-model.number="int" /> 复制代码
- .trim 自动过滤用户输入的首尾空格
<input type="text" v-model.trim="int" /> 复制代码
键盘事件的按键别名
- .enter 回车触发事件
<input type="text" v-model.trim="int" @keyup.enter="enterChange" /> 复制代码
- .tab tab键
- .esc esc键
- .space 空格键
- up 上键
- .down 下键
- .left 左键
- .right 右键
如若在elementUI上直接写键盘事件是不生效的,因为elmentUI对input进行了封装,原生的事件不起作用,如果想要原生事件,在后面加上.native
-
<input type="text" v-model.trim="int" @keyup.enter.native="enterChange" /> 复制代码
键盘事件的修饰符
-
.ctrl
<input type="text" v-model.trim="int" @keyup.enter.ctrl="enterChange" /> <!-- 同时按下ctrl和回车键触发事件 以下同理 -> 复制代码
-
.alt
-
.shift
-
.meta
-
.stop:阻止事件继续传播,阻止冒泡和捕获
<a v-on:click.stop="doThis"></a> 复制代码
-
capture::将默认的冒泡事件修改为捕获。
<div @click.capture="fn('父元素')"> 父元素 <div @click.capture="fn('子元素')">子元素 <div @click='fn('孙子元素')'>孙子元素</div> </div> </div> methods:{ //默认是先打印孙子元素 子元素 再打印父元素 fn(val){ console.log(val) // 在父元素加上.capture后,先打印父元素,打印孙子元素,在打印孙子元素。这个只是改变单个元素的顺序 //父元素和子元素加上.capture后,先打印父元素,打印子元素,最后打印孙子元素 } } 复制代码
-
.prevent: 阻止默认事件
- 可用于阻止浏览器上鼠标右键点击出现弹窗
<div @contextmenu.prevent="fn(3)">孙子元素</div> //如果没有写.prevent 会触发fn事件,fn执行完成后,还会触发默认的鼠标右键事件, //在浏览器上会自动弹出弹窗 复制代码
- 可阻止form表单中submit按钮点击刷新页面的问题
- 可阻止a标签点击跳转的问题
- 可用于阻止浏览器上鼠标右键点击出现弹窗
-
.once:事件只触发一次
-
.self:通过捕获或者冒泡触发时,只有点击本元素才会触发。即事件不是从内部元素触发的
<div @click="fn('父元素')">
父元素
<div @click.self="fn('子元素')">子元素
<div @click='fn('孙子元素')'>孙子元素</div>
</div>
</div>
methods:{
//默认是先打印孙子元素 子元素 再打印父元素
fn(val){
console.log(val) //在孙子元素加上.self后,先打印孙子元素,再打印父元素。不会触发子元素的事件
}
}
//捕获同理不会触发self的元素事件
复制代码
- .passive
- passive主要用在移动端的scroll事件,来提高浏览器响应速度,提升用户体验。因为passive=true等于提前告诉了浏览器,touchstart和touchmove不会阻止默认事件,手刚开始触摸,浏览器就可以立刻给与响应;否则,手触摸屏幕了,但要等待touchstart和touchmove的结果,多了这一步,响应时间就长了,用户体验也就差了。
- 支持多个修饰符搭配使用
computed和watch的区别和运用场景
- computed是计算属性
- 依赖于其他属性,一个计算属性的函数可以同时依赖多个属性,并且computed值有缓存,只有它所依赖的值发生改变,computed的值才会发生改变。
- 不支持异步,当computed内有异步操作时无效,无法监听数据变化。
- 应用场景:简化template里{{}}的计算;处理props和$emit的传值。主要用来处理数据。如果仅是简单的处理数据,我们优先使用computed,而不是methods,因为有缓存,性能优化。
- 用法
<template> <div class="box"> <input v-model='age' /> {{info}} </div> </template> <script> export default { data(){ return { text:'Hello', age:'15', weight:'50', information:'' } }, computed:{ //简易写法 这个是计算属性的,getter的方法。计算属性默认只有getter属性,不过他也提供了设置属性setter info(){ return `${this.text}${this.age}` }, isHealthy:{ get(){ return this.weight>50? '健康':'亚健康' }, set(val){ //当我们改变isHealthy的值时,触发这个函数。 console.log(val) //改变的值 } } } } </script> 复制代码
计算属性VS方法
-
使用函数方法也可以实现计算属性的效果,为什么使用计算属性
<div>{{countTotal()}}</div> <div>{{total}}</div> data(){ return{ number:10, unitPrice:5, total:0 } }, computed:{ total(){ return this.number + this.unitPrice } }, methods:{ countTotal(){ return this.number + this.unitPrice } } 复制代码
- 计算属性是基于他的响应式依赖进行缓存的,只有在相关的依赖值发生变化时,才会重新计算,这意味着如果this.number和this.unitPrice这两个如果都没有变化时,多次访问这个页面,计算属性会直接返回之前计算的结果,不会重新计算,而methods每次进入这个页面都会重新计算,影响性能。性能优化可以有。
-
watch是侦听属性
- 依赖于其他属性,一个侦听函数中,只能侦听一个依赖属性,依赖属性改变,直接触发响应的操作。
- 没有缓存,更多的是观察者的作用。
- 支持异步。
- 应用场景:当我们需要在数据变化时,执行异步或者开销较大的操作时。比如请求接口等。
- 用法
<template> <div class="home"> <input v-model="psd" /> {{msg}} </div> </template> <script> export default { data(){ return{ psd:"", msg:'', obj:{ a:1 } } }, watch:{ //监听psd;如若psd发生变化则触发函数 //可以不传参 psd(){ //变化后的操作 }, //传参 pad(newVal,oldVal){ //newval新改变的数,这里也就是pad,oldVal之前的数 setTimeout(()=>{ if(newVal.length>4){ this.msg = '密码太长'; return }; if(newVal.length<3){ this.msg="密码太短"; return }; this.msg = '' },1000) }, // 当监听的是一个对象时,默认情况下,handler只监听了属性引用的变化,也就是只监听了一层, 但对象内部的属性时监听不到的,所以我们需要添加deep:true;才能监听到对象里属性的变化; 但是这也非常耗性能。 obj(){ handler(){ console.log("obj.a发生改变") }, deep:true, }, //immediate 初始化就会执行一遍监听 obj(){ handler(){ //执行需要的操作 }, immediate:true, }, //监听对象的具体属性,就不需要设置deep为true了 obj.a(){ handler(){ //执行需要的操作 } } } } <script> 复制代码
-
注意:不要在computed或者watch中修改所依赖的值,否则会陷入死循环。
vue不能检测哪些数组的变动;怎么用$set解决对象新增属性不能被响应的问题。
- 由于js的限制,vue不能检测到以下数组的变动。
- 利用索引直接修改或者赋值数组项时
- 修改数组长度时
- 由于js的限制,vue不能检测到对象属性的添加或者删除。
- 解决办法如下:
<template>
<div class='main'>
{{obj}}{{ary}}
</div>
</template>
<script>
export default {
data(){
return{
obj:{},
ary:[1,2,3]
}
},
mounted(){
this.obj.a = '你好'//虽然说console.log()已经更新,但是view层并没有更新。
页面展示的是一个空的对象
this.ary[2] = 5 //和obj一样,view层并没有更新,页面展示[1,2,3]
//解决vue数组变动检测不到的问题。
//1、通过$set解决
this.$set(ary,2,5)//
//2、通过splice解决
this.ary.splice(2,1,5)//ary数组第二索引开始往后第一个替换成5
//解决vue中对象属性增删检测问题
//1、通过$set解决
this.$set(obj,'a',[1,2,3,4,5,6])
}
}
</script>
复制代码
对vue项目进行过哪些优化
代码层优化
多个if判断如何优雅的写
- 上代码
function fn(item){
if(a){
console.log(a)
}else if(b){
console.log(b)
}else if(c){
console.log(c)
}
}
//优化方案一
function fn(item){
let arr = [a,b,c]
arr.includes(item)? console.log(item):return
}
//优化方案二
function fn(item){
let obj = {
a:fn1
b:fn2
c:fn3
}
obj[item]()
}
复制代码
- v-if和v-show区分场景使用;v-show在频繁切换的时候使用。
- computed和watch区分场景使用,computed处理简单数据时使用;watch处理异步和繁杂的操作。
- v-for时使用:key,同时避免使用v-if;v-for的优先级比v-if高,导致每个循环都有v-if操作,耗性能;key添加唯一标识,让diff的更准确、更快速。
- 图片懒加载
- 第三方插件按需引入;例如element-ui
- 首先安装element-ui依赖
npm i element-ui -S
- 利用babel-plugin-component,
npm i babel-plugin-component -D
- 在src同级文件下创建.babelrc文件夹写入以下配置
{ "presets":[["es2015",{"module":false}]], "plugins":[ [ "component", { "libraryName":"element-ui", "styleLibraryName":"theme-chalk" } ] ] } 复制代码
- 在main.js中按需引入
import Vue from 'vue'; import { Btton, Select} from 'element-ui'; Vue.use(Btton); Vue.use(Select); 复制代码
- 路由懒加载
6.防抖节流{ path:'/home', component:()=>import('./home'), } 复制代码
- 首先安装element-ui依赖
- webpack层面优化
- webpack对图片压缩
- 减少ES6转为ES5的冗余代码
- 提取公共代码
- 提取组件的css
- 基础的web技术优化
- 开启gzip压缩
- 浏览器缓存
- CDN的使用
说说你对SPA单页面的理解,它的优缺点是什么?
- SPA紧在页面初始化时加载html、js、css。一旦页面完成,SPA不会因为用户的操作而进行页面的重新加载;取而代之的是利用路由机制实现html的内容变换。
- 优点:
- 用户体验好,快,内容的改变不会重新加载整个页面;避免了不必要的跳转和重新渲染。
- 相对于服务器的压力也较小
- 前后端职责分离,架构清晰,前端负责交互逻辑,后端负责数据处理。
- 缺点
- 初次加载耗时较长,需要加载页面的时候将js、css统一加载,部分页面按需加载。
vue中slot的使用
- slot插槽可以理解为一个占位符,他的可以在组件内添加元素及内容,自定义的组件在其中间填写内容是不会展示的,这时候我们可以用插槽解决。
- 默认插槽
//先创建一个子组件
<template>
<div class = "son">
<p>我是子组件插槽</p>
<slot></slot>
</div>
</template>
//父组件
<template>
<div class = "father">
<p>我是父组件插槽</p>
<Son>吊毛刘</Son>
</div>
</template>
import Son from '@/components/Son'
<script>
export default {
components: {
Son
}
}
</script>
//页面展示
//我是父组件插槽
//我是子组件插槽
//吊毛刘
复制代码
- 具名插槽
//子组件
<template>
<div>
我是子组件插槽
<slot name=header></slot>
<slot name="footer"></slot>
</div>
</template>
//父组件
<template>
<div class = "father">
<template name="header">
一入前端深似海
</template>
<P name='footer'>有毛病</p>
</div>
</template>
import Son from '@/components/Son'
<script>
export default {
components: {
Son
}
}
</script>
//页面显示
//我是子组件插槽
//一入前端深似海
//有毛病
复制代码
vue过渡和动画
过渡
- vue在插入、移除、更新DOM时,提供多种不同方式的应用过渡效果。vue提供了内置的过渡封装组件,该组件用于包裹实现过渡效果的组件。
- 先上波实例,了解transiton的语法。
<div class= 'home'>
<button @click = "show = !show">开始变色</button>
<transition name = 'fade'>
<div class = "font">颜色变变变</div>
</transition>
</div>
export default {
data(){
return{
show:true
}
}
<style lang = "less" scope>
.font{
color:balck;
opacity:1;
}
.fade-enter-active,.fade-leave-active{
transition:all 2s
}
.fade-enter,.fade-leave-to{
color:yellow
}
</style>
复制代码
- 过渡其实就是一个淡入淡出的效果,vue在元素的显示隐藏的过渡中,提供了6个clss来切换
- v-enter:定义进入过渡的开始状态,在元素插入之前生效,在元素被插入的下一帧移除。
- v-enter-active:定义过渡生效时的状态。在元素被插入之前生效,在整个进入过渡的阶段应用,在过渡/动画完成之后移除,这个类一般用来定义进入过渡过程的时间、延迟和曲线函数。
- v-enter-to:定义进入过渡的结束状态,在元素被插入之后的下一帧移除,在过渡/动画完成之后移除。
- v-leave:定义离开过渡的开始状态,在离开过渡被触发时立刻生效,下一帧被移除。
- v-leave-active:定义离开过渡生效时的状态,在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在离开动画/过渡完成之后移除,这个类可以用来定义离开过渡的时间、延迟和曲线函数。
- v-leave-to:定义离开过渡的结束状态,在过渡/动画完成之后移除。
- 对于这些过渡中切换的类名来说,如果你使用一个没有名字的
<transition>
,则v-是这些类名默认的前缀。如果使用<transition name = "name">
,那么直接在样式里用name-类名即可
- 自定义过渡类名
- enter-class
- enter-active-class
- leave-active-class
- leave-class
- 可以结合animation插件设置类名这样就不用写@keyframes
动画
- 例子
<div class= 'home'>
<button @click = "show = !show">开始变色</button>
<transition name = 'fade'>
<div class = "font">颜色变变变</div>
</transition>
</div>
export default {
data(){
return{
show:true
}
}
<style lang = "less" scope>
.font{
color:balck;
opacity:1;
}
.fade-enter-active,.fade-leave-active{
animation:bounce 2s
}
//因为有0%,所以不用设置初始值,也就是v-enter和v-leava-to
@keyframes bounce {
0%{
color:balck
}
25%{
color:yellow
}
50%{
color:red
}
100%{
color:black
}
}
</style>
复制代码
使用require.context实现前端工程自动化
- 在公共组件中创建index.js文件,写入以下代码,然后在main.js中引入index.js文件,这样就不需要在页面在引入公共组件和注册组件了,比如组件名GvDialog,直接用
<gv-dialog/>
,如果是GvSearchTable则为<gv-search-table/>
即可,注意格式必须如示例哦。
import Vue from 'vue';
const req = require.context(
// 其组件目录的相对路径
'./',
// 是否查询其子目录
false,
// 匹配基础组件文件名的正则表达式 匹配.vue结尾的文件
/.vue$/);
const requireAll = file =>
//将处理好的moudules添加进空对象中,e为当前项
file.keys().reduce((modules, e) => {
//modules下方设置为空对象,这里modules对象,用组件名当为key,value为组件信息 file(e).default
//将每一个组件对象添加进modules
modules[e.match(/\w+.vue$/)[0].slice(0, -4)] = file(e).default;
return modules;
}, {});
const components = requireAll(req); //处理好的组件对象信息
//循环对象
for (var key in components) {
var k = key.replace(/[A-Z]/g, (e, i) => {
//将组件名的驼峰命名改为小写,用-隔开
if (i === 0) {
return e.toLowerCase();
} else {
return '-' + e.toLowerCase();
}
});k
//定义全局组件,将处理好的组件名字定位为全局组件名,组件为组件信息components[key]
Vue.component(k, components[key]);
}
复制代码