本文已参与「新人创作礼」活动,一起开启掘金创作之路。
1、vue2生命周期?
1. 生命周期
Vue实例完整的生命周期,开始创建--> 初始化数据 --> 编译模板 --> 挂载DOM --> 渲染,更新 --> 渲染,卸载等一系列过程。
1. new Vue()
2. 初始化事件,生命周期
- Vue构造函数下的initMixin()方法中,initEvents,initLifecycle,initRender
触发beforeCreate钩子函数 ,访问不到data,computed等的方法和数据
3. 初始化注入,校验
- initMixin中initInjections,initProvide,initState(initData是observe响应式入口)
- 双向数据绑定,data响应式变化在此完成
触发created钩子函数 ,实例配置上的options包括data,computed等配置完成,可以访问各种数据,获取接口数据等;但是渲染节点还未挂载到DOM,所以不能访问 $el属性
4. 判断有无el,没有的话直到el挂载;有的话判断有无template,没有的话,获取el中的outHTML;有的话转为render
- 重写
$mount,重新获取el,执行mountComponent()方法,进入方法,创建vm.$el - 将模板编译为render函数
触发beforeMount钩子函数
5. 创建vm.$el并替换el
- 执行updateComponent方法
- 首次执行,将
vm.$el真实DOM与Vnode比较,并存放在$el中 vm._update(vm._render())将 Vnode转为真实DOM 并渲染到界面
触发mounted钩子函数,此时已经是真实DOM
6. 当data发生变化时,触发beforeUpadate钩子函数
7. 响应式数据更新,旧的Vnode与新的Vnode比较,渲染成真实DOM,派发patch
触发updated钩子函数,DOM已经更新完成
8. 当调用destroy时,触发beforeDestroy钩子函数,this仍能获取到实例
9. 实例销毁,事件子组件等都被移除销毁
触发destroyed钩子函数
2. created和mounted的区别?
- created 初始化属性,进行了双向绑定;模板渲染成html前调用
- mounted 初始化页面完成,模板已经渲染成html,vnode转为真实DOM渲染到界面上。
3. Vue有父子组件时的生命周期?
创建,从外到里,渲染,从里到外
-
加载渲染:父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
-
更新过程:父beforeUpdate->子beforeUpdate->子updated->父updated
-
销毁过程:父beforeDestroy->子beforeDestroy -> 子destroyed -> 父destroyed
4. 一般在哪个生命周期请求异步数据?
我们可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。
推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
-
能更快获取到服务端数据,减少页面加载时间,用户体验更好;
-
SSR不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性。
5. keep-alive的生命周期有哪些?
设置了keep-alive会增加两个生命周期钩子(activated与deactivated),同时,beforeDestroy 和 destroyed 就不会再被触发了,因为组件不会被真正销毁。
-
不使用keep-alive:beforeRouteEnter --> created --> mounted --> destroyed
-
使用keep-alive:beforeRouteEnter --> created --> mounted --> activated ... > beforeRouteLeave --> deactivated
-
使用keep-alive再次进入缓存页面:beforeRouteEnter --> activated .. > beforeRouteLeave --> deactivated
2、watch 和 computed 的区别?
- 是否缓存: computed支持缓存,computed依赖的值data或者props传过来的值,发生变化时才会计算;watch不支持缓存,watch第一次不执行,只有值发生变化时才会执行,如果需要立即执行可设置,immediate: true;watch 默认是浅监听,可以进行深度监听,deep: true
- 是否支持异步: computed不支持异步;watch支持异步;
- 运用场景: 当需要数值计算,有依赖值时使用computed;数据变化时执行异步或者开销较大时,使用watch
<template>
<div>
<p>{{ name }}</p>
<button>改变值类型watch</button>
<p>{{ msg.info }}</p>
<button>改变引用类型watch</button>
<p>{{ num }}</p>
<p>{{ sum }}</p>
</div>
</template>
<script>
export default {
name: 'watch-demo',
data() {
return {
name: '值类型',
msg: {
info: '引用类型'
},
}
},
watch: {
// watch默认是浅度监听
name(val, oldVal) {
console.log(val, oldVal, '值类型')
},
// wacth进行深度监听需要添加deep: true;不能获取到oldVal
msg: {
handler(val, oldVal) {
console.log(val, oldVal, '引用类型')
},
deep: true, // 深度监听
immediate: true // 立即执行,watch第一次绑定不执行,只有值发生变化才会执行,如果需要立即执行需要为true
}
},
computed: {
sum() {
console.log(this.num,'this.num')
return this.num * 2;
}
},
methods: {
watchName() {
this.name = '改变后的值类型';
},
watchMsg() {
this.msg.info = '改变后的引用类型';
}
}
}
</script>
<style scoped lang='less'>
.nameClass {
font-size: 20px;
}
.nameColor {
color: #333;
}
</style>
3、class和style用法?
<template>
<div>
<p :class="{nameClass: isTrue, nameColor: isColor}">{{ name }}</p>
<button :class="[isTrue ? 'nameClass' : '']">改变值类型watch</button>
<p :style="{fontSize: '18px'}">{{ msg.info }}</p>
<button :style="[baseStyle, overStyle]">改变引用类型watch</button>
</div>
</template>
<script>
export default {
name: 'watch-demo',
data() {
return {
name: '值类型',
msg: {
info: '引用类型'
},
isTrue: true,
isColor: true,
baseStyle: {
width: '300px',
height: '30px'
},
overStyle: {
fontSize: '20px'
}
}
},
}
</script>
<style scoped lang='less'>
.nameClass {
font-size: 20px;
}
.nameColor {
color: #333;
}
</style>
4、条件渲染v-if、v-show的区别?
-
v-if第一次是false时不会渲染,只有当变成true时才会渲染,会进行dom的重构与销毁;v-show是dom一直存在,只是dispaly的显示与隐藏
-
v-if 用于更新不是很频繁的;v-show用于经常切换的场景
5、v-if、v-show、v-html的原理?
-
v-if会调用addIfCondition方法,生成Vnode节点时会忽略v-if为false的节点,render的时候不会渲染
-
v-show会生成Vnode节点,render也会渲染为真实节点,只是render过程中节点的属性display为none
-
v-html是将innerHTML作为v-html的值,会先移除节点下的所有节点,调用html方法,添加innerHTML属性。
6、循环渲染v-for为什么要加key?
加上key以后,在Vnode转为真实DOM的时候,diff算法判断sameVnode是否相同就是判断key是否相同,tag是否相同,使用key减少了DOM操作
7、v-for可以与v-if一起使用吗?为什么?
不推荐一起使用,v-for的优先级高于v-if,这样v-if会出现在每个v-for中,可以在上一层div中使用v-if,这样先进行判断是否显示,再进行循环,但只想渲染一部分数据时,这样使用很方便
8、组件间的传递方式?
1. 父传子: props
-
props只能父组件向子组件传值,子组件的数据会随着父组件不断更新。 -
props可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。 -
props属性名规则:若在props中使用驼峰形式,模板中需要使用短横线的形式
父组件:
<template>
<div>
<Emit :number="num"></Emit>
</div>
</template>
<script>
import Emit from './Emit.vue'
export default {
name: "item-demo",
data() {
return {
num: 1,
}
},
components: {
Emit
}
}
</script>
子组件:
<template>
<div>
<h5>父传子:</h5>
<h5>{{number}}</h5>
</div>
</template>
<script>
export default {
name: "emit",
props: {
number1: {
type: 'Number',
default: () => {
return 0
}
},
}
}
</script>
2. 子传父: this.$emit
$emit绑定一个自定义事件,当这个事件被执行的时就会将参数传递给父组件,而父组件通过v-on监听并接收参数。
父组件:
<template>
<div>
<!--btnclick不写参数,则默认直接传入子组件传入的id,浏览器事件(例onclick)若不写参数,默认传入的是event事件-->
<Emit @emitclick="btnclick"></Emit>
</div>
</template>
<script>
import Emit from './Emit.vue'
export default {
name: "item-demo",
components: {
Emit
},
methods: {
btnclick(id) {
console.log('btnclick',id)
}
}
}
</script>
子组件:
template>
<div>
<h5>子传父:</h5>
<ul>
<li
v-for="item in categories"
:key="item.id"
@click="btnclick(item.id)"
>
{{item.name}}
</li>
</ul>
</div>
</template>
<script>
export default {
name: "emit",
data() {
return {
categories:[
{id:'1',name:'热门推荐'},
{id:'2',name:'手机数码'},
{id:'3',name:'家用家电'},
]
}
},
methods: {
btnclick(id) {
//$emit('事件名',传入的值)
this.$emit('emitclick',id)
}
}
}
</script>
3. 兄弟组件: eventBus
eventBus事件总线适用于父子组件、非父子组件等之间的通信,使用方式:
import eventBus from './eventBus'
eventBus.$on('')
eventBus.$emit()
eventBus.$off()
使用步骤如下:
- 创建事件中心管理组件之间的通信
eventBus.js
import Vue from 'Vue'
export default new Vue()
- 发送事件 有两个兄弟组件,first,second
first.vue:
<template>
<div>
<button @click="add">加法</button>
</div>
</template>
<script>
// 引入事件中心
import eventBus from './eventBus'
export default {
data() {
return {
num:0
}
},
methods:{
add() {
eventBus.$emit('addition', {
num:this.num++
})
}
}
}
</script>
- 接收事件 second.vue:
<template>
<div>
求和: {{ count }}
</div>
</template>
<script>
// 引入事件中心
import eventBus from './eventBus'
export default {
data() {
return {
count:0
}
},
mounted() {
EventBus.$on('addition', param => {
this.count = this.count + param.num;
})
}
}
</script>
在上述代码中,这就相当于将num值存贮在了事件总线中,在其他组件中可以直接访问。事件总线就相当于一个桥梁,不同组件通过它来通信。
如果项目过大,使用这种方式进行通信,后期维护起来会很困难。
4. 父组件获取子组件内容:$children,$refs / ref
-
一般不用$children,因为不能保证顺序,需要使用下标值来取值,要是改变需求时就需要经常改动,不方便
-
使用$refs,需要在使用组件时加上ref属性
父组件:
<template>
<div>
<Emit ref="aaa"></Emit>
<button @click="showRef">点击显示子组件ref内容</button>
</div>
</template>
<script>
import Emit from './Emit.vue'
export default {
name: "item-demo",
components: {
Emit
},
methods: {
showRef() {
console.log(this.$refs.aaa.name);
}
}
}
</script>
子组件:
export default {
name: "emit",
data() {
return {
name: '我是子组件Emit的name',
}
},
}
5. 子组件获取父组件内容:$parent,$root
一般不用$parent,因为在开发中,一个子组件可能有好几个父组件,使用$parent耦合性太高,所以一般不使用;可以使用$root来访问根组件的实例
6. 父子组件依赖注入:provide,inject
这种方式是Vue中的 依赖注入,该方法用于 父子组件之间的通信。这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方法来进行传值。就不用一层一层的传递了。
provide / inject是Vue提供的两个钩子,和data、methods是同级的。并且provide的书写形式和data一样。
provide钩子用来发送数据或方法inject钩子用来接收数据或方法
父组件:
provide() {
return {
num: this.num
}
}
子组件:
inject: ['num']
还可以这样写,这样写就可以访问父组件中的所有属性:
provide() {
return {
app: this
}
}
data() {
return {
num: 1
}
}
inject: ['app']
注意: 依赖注入所提供的属性是非响应式的。
7. $attrs,$listeners
考虑一种场景,如果A是B组件的父组件,B是C组件的父组件。如果想要组件A给组件C传递数据,这种隔代的数据,该使用哪种方式呢?
- 如果是用
props/$emit来一级一级的传递,确实可以完成,但是比较复杂; - 如果使用事件总线,在多人开发或者项目较大的时候,维护起来很麻烦;
- 如果使用Vuex,的确也可以,但是如果仅仅是传递数据,那可能就有点浪费了。
针对上述情况,Vue引入了$attrs / $listeners,实现组件之间的跨代通信。
先来看一下inheritAttrs,它的默认值true,继承所有的父组件属性除props之外的所有属性;inheritAttrs:false 只继承class属性 。
$attrs:继承所有的父组件属性(除了prop传递的属性、class 和 style ),一般用在子组件的子元素上$listeners:该属性是一个对象,里面包含了作用在这个组件上的所有监听器,可以配合v-on="$listeners"将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件)
A组件(APP.vue):
<template>
<div id="app">
//此处监听了两个事件,可以在B组件或者C组件中直接触发
<child1 :p-child1="child1" :p-child2="child2" @test1="onTest1" @test2="onTest2"></child1>
</div>
</template>
<script>
import Child1 from './Child1.vue';
export default {
components: { Child1 },
data() {
return {
child1: 'B组件',
child2: 'C组件'
}
},
methods: {
onTest1() {
console.log('test1 running');
},
onTest2() {
console.log('test2 running');
}
}
};
</script>
B组件(Child1.vue):
<template>
<div class='child-1'>
<p>props: {{pChild1}}</p>
<p>$attrs: {{$attrs}}</p>
<child2 v-bind="$attrs" v-on="$listeners"></child2>
</div>
</template>
<script>
import Child2 from './Child2.vue';
export default {
components: { Child2 },
props: {
pChild1: {
type: 'String',
default: () => {
return ''
}
}
},
inheritAttrs: false,
mounted() {
// 触发APP.vue中的test1方法
this.$emit('test1');
}
};
</script>
C组件(Child2.vue):
<template>
<div class='child-2'>
<p>props: {{pChild2}}</p>
<p>$attrs: {{$attrs}}</p>
</div>
</template>
<script>
export default {
props: {
pChild2: {
type: 'String',
default: () => {
return ''
}
}
},
inheritAttrs: false,
mounted() {
// 触发APP.vue中的test2方法
this.$emit('test2');
}
};
</script>
在上述代码中:
- C组件中能直接触发test的原因在于 B组件调用C组件时 使用 v-on 绑定了
$listeners属性 - 在B组件中通过v-bind 绑定
$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的)
9、子组件可以直接修改父组件的数据吗?
不行,因为在vue中是单向数据流,父组件通过props传给子组件数据,父组件中的数据发生变化时,子组件也会更新。为了防止意外改变父组件状态,使数据流变得难以理解,导致数据流混乱。
10、v-model如何实现的,语法糖是什么?v-model可以被用在自定义组件上吗?如果可以,如何使用?
1. 作用在表单元素上
动态绑定了input的value,指向了message变量,并且在触发input事件的时候去动态把message设置为目标值:
- $event 当前触发的事件对象
- $event.target 当前触发的事件对象的dom
- $event.target.value 当前dom的value值
<input v-model="message" />
// 相当于
<input
v-bind:value="message"
v-on:input="message = $event.target.value
>
2. 作用在组件上
通过prop和$emit实现,语法糖是:
- 把value用作prop
- 把input用作event
(1) 父组件使用子组件自定义的v-model
<template>
<div>
<p>{{ name }}</p>
<CustomVModel v-model="name"></CustomVModel>
</div>
</template>
<script>
import CustomVModel from './customVModel.vue'
export default {
name: 'modelIndex',
data() {
return {
name: '自定义v-model'
}
},
components: {
CustomVModel
}
}
</script>
(2) 子组件使用input来实现v-model
<template>
<div>
<input
type="text"
:value="text1"
@input="$emit('change1', $event.target.value)"
/>
<!--
1. 使用value而不是v-model
2. change1与model.event对应起来
3. vaule的text1与model.prop对应
-->
</div>
</template>
<script>
export default {
name: 'customVModel',
data() {
return {
}
},
model: {
prop: 'text1', // 与input中的value值对应
event: 'change1' // 与@input中的方法名对应
},
props: {
text1: {
type: String,
default: () => {
return ''
}
}
}
}
</script>
11、$nextTick原理及作用?
1. $nextTick
vue是异步渲染,data改变后,DOM不会立即渲染,$nextTick会在DOM渲染后触发,以获取最新DOM节点,nextTick() 也用作优化。
使用场景:
-
修改数据后立刻获得 更新后的DOM结构 ,可以使用nextTick()
-
在vue生命周期中,如果在 created() 钩子函数进行 DOM操作 ,也一定要放在nextTick() 的回调函数中,因为created()钩子函数中,DOM还未渲染,这时候也没办法操作DOM,所以要操作DOM,必须放在nextTick() 中。
<template>
<div>
<ul ref='ulref'>
<li v-for="(item,index) in list" :key="index">
{{ item }}
</li>
</ul>
<button @click="addItem">添加数据</button>
</div>
</template>
<script>
export default {
name: 'nextTick',
data() {
return {
list: ['a', 'b', 'c']
}
},
methods: {
addItem() {
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
// 这样写的结果是:点击一次添加输出3,点击两次输出5,期望第一次得到5,第二次为7
// 获取DOM元素,this.$refs.名字
const urItem = this.$refs.ulref;
console.log(urItem.childNodes.length)// 3,5....
// 1. 异步渲染,$nextTick 等 DOM 渲染完再回调
// 2. 页面渲染会将data的修改做整合,多次data修改只渲染一次
this.$nextTick(() => {
const urItem = this.$refs.ulref;
console.log(urItem.childNodes.length)// 5,7....
})
}
}
}
</script>
2. 原理分析
12、slot是什么?有什么作用?原理是什么?
1. slot插槽
slot插槽,使组件具有扩展性。插槽slot是子组件的一个模板标签元素,这个元素是否显示,及怎么显示,由父组件决定。例如title标签,现有的是有title标题,但是也可能有图标,或者按钮等,这时候可以使用插槽。
插槽类型:
- 默认插槽: 也叫匿名插槽,slot没有指定name属性,一个组件内只能有一个匿名插槽
子组件:
<template>
<div class="slot-main">
<div>
{{message}}
<br>
<slot><button>默认值按钮</button></slot>
</div>
</div>
</template>
<script>
export default {
name: 'slot-demo',
data() {
return {
message: '我是子组件插槽Slot',
}
}
}
</script>
父组件:
<template>
<div>
<Slot>
<span>更改默认值</span>
</Slot>
</div>
</template>
- 具名插槽: 带有name具体名字的插槽,一个组件可以出现多个具名插槽
子组件:
<template>
<div class="slot-main">
<div>
{{message}}
<br>
<slot><button>默认值按钮</button></slot><br>
<slot name="center"><span>具名插槽</span></slot><br>
<slot name="right"><span>需要name</span></slot>
</div>
</div>
</template>
<script>
export default {
name: 'slot-demo',
data() {
return {
message: '我是子组件插槽Slot',
}
}
}
</script>
父组件:
<template>
<div>
<Slot>
<template v-slot:center>
<span>具名插槽父组件更改时用v-slot:name</span>
</template>
</Slot>
</div>
</template>
- 作用域插槽: 可以是匿名插槽,也可以是具名插槽,不同点是在子组件渲染作用于插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件传递过来的数据决定如何渲染该插槽
子组件:
<template>
<div class="slot-main">
<div>
<slot v-bind:user="planguages">
<p v-for="item in planguages" :key="item">
{{ item }}
</p>
</slot>
</div>
</div>
</template>
<script>
export default {
name: 'slot-demo',
data() {
return {
planguages: ['js', 'vue', 'ts']
}
}
}
</script>
父组件:
<template>
<div>
<Slot>
<template v-slot:default="slot">
<span v-for="item in slot.user" :key="item">
{{ item }}-
</span>
</template>
</Slot>
</div>
</template>
2. 原理分析
当子组件vm实例化时,获取到父组件传入的slot标签的内容,存放在vm.$slot中,默认插槽为vm.$slot.default,具名插槽为vm.$slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到slot标签,使用$slot中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。
13、动态组件
动态组件,需要使用v-bind:is来绑定组件名
<template>
<div>
<div v-for="(val,key) in listInfo" :key="key">
<!-- :is动态绑定组件名,不能直接绑定组件 -->
<component :is='val.type'></component>
</div>
</div>
</template>
<script>
import NextTick from './nextTick.vue'
export default {
name: 'isComponent',
data() {
return{
listInfo: {
1: {
type: 'NextTick' // 组件名
},
2: {
type: 'text'
},
3: {
type: 'img'
}
}
}
},
components: {
NextTick
}
}
</script>
14、异步加载组件
当一个组件特别大的时候,通过某一触发条件加载时,可以使用异步加载
<template>
<div>
<button @click="loadAsync">点击加载异步组件</button>
<NextTick v-if="show"></NextTick>
</div>
</template>
<script>
// import NextTick from './nextTick.vue'
export default {
name: 'importCom',
data() {
return {
show: false
}
},
components: {
// 异步加载组件
NextTick: () => import('./nextTick.vue')
},
methods: {
loadAsync() {
this.show = true
}
}
}
</script>
15、如何保存页面的当前状态?
1. localStorage / sessionStorage
通过localStorage / sessionStorage 把当前状态通过JSON.stringify()保存。
优点:
- 兼容性好,不需要额外工具
- 简单快捷,基本可以满足大部分需求
缺点:
- 状态通过 JSON 方法储存(相当于深拷贝),如果状态中有特殊情况(比如 Date 对象、Regexp 对象等)的时候会得到字符串而不是原来的值。
2. keep-alive
当组件在keep-alive内被切换时组件的状态会被保留
例如在项目中,切换两个tab,想要保存当前组件的状态
<keep-alive>
<Master v-if="tab === 'master'" :tab='tab' :isDetail='isDetail'></Master>
<Heavywork v-if="tab === 'heavywork'" :tab='tab' :isDetail='isDetail'></Heavywork>
</keep-alive>
16、对keep-alive的理解,它是如何实现的,具体缓存的是什么?
1. keep-alive是什么?
keep-alive保存组件当前状态到内存中,在切换回来后仍有当前状态,防止重复渲染DOM。keep-alive缓存动态组件时会缓存不活动的组件实例,而不是销毁他们。
设置了keep-alive会增加两个生命周期钩子(activated与deactivated):
-
不使用keep-alive:beforeRouteEnter --> created --> mounted --> destroyed
-
使用keep-alive:beforeRouteEnter --> created --> mounted --> activated ... > beforeRouteLeave --> deactivated
-
使用keep-alive再次进入缓存页面:beforeRouteEnter --> activated .. > beforeRouteLeave --> deactivated
keep-alive可以设置以下props属性:
-
include: 字符串或正则表达式。只有名称匹配的组件会被缓存
-
exclude: 字符串或正则表达式。任何名称匹配的组件都不会被缓存
-
max: 数字。最多可以缓存多少组件实例 匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值),匿名组件不能被匹配。
2. 使用场景:
使用原则:当我们在某些场景下不需要让页面重新加载时我们可以使用keepalive
例如:
当我们从首页–>列表页–>商品详情页–>再返回,这时候列表页应该是需要keep-alive
从首页–>列表页–>商品详情页–>返回到列表页(需要缓存)–>返回到首页(需要缓存)–>再次进入列表页(不需要缓存),这时候可以按需来控制页面的keep-alive
在路由中设置keepAlive属性判断是否需要缓存
{
path: '/',
name: 'xxx',
component: () => import('../src/pages/xxx.vue'),
meta: {
keepAlive: true // 需要被缓存
}
}
使用keep-alive:
<div>
<keep-alive>
<!-- 需要缓存的组件 -->
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<!-- 不需要缓存的组件 -->
<router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
3. 原理分析:
keep-alive是vue中内置的一个组件
-
该组件没有template,而是用了render,在组件渲染的时候会自动执行render函数。
- 首先获取组件的key值
- 拿到key值去this.cache对象中寻找是否有该值,如果有则表示该组件有缓存,即命中缓存;如果没有,以该组件的key为键,将其存入到this.cache中,并且把key存入到this.keys中
- 判断this.keys中缓存组件的数量是否超过了设置的最大缓存数this.max,如果超过了,则把第一个缓存组件删掉
-
在mounted钩子函数中观测include和exclude的变化,如果发生变化,说明需要缓存的组件或者不需要缓存的组件发生了变化,执行pruneCache函数
-
该函数对this.cache遍历,取出name值与新的缓存规则匹配,如果匹配不上,则表示在新的缓存规则下该组件已经不需要被缓存,则调用pruneCacheEntry函数将其从this.cache对象剔除即可
keep-alive具体是通过 cache 数组缓存所有组件的 vnode实例。当cache内原有组件被使用时会将该组件key从keys数组中删除,然后push到keys数组最后,以便清除最不常用组件。
4. 缓存后如何获取数据
- beforeRouteEnter
beforeRouteEnter(to, from, next) {
next(vm => {
console.log(vm)
// 每次进入路由执行
vm.getData() // 获取数据
})
}
- activated
activated() {
this.getData() // 获取数据
}
注意:服务器端渲染期间avtived不被调用
17、过滤器的作用,如何实现一个过滤器?
filters过滤器,不会修改数据,而是过滤数据(计算属性computed,方法methods都是通过修改数据来处理数据格式的输出显示)。使用方法是在插值表达式 {{ }}中,放在操作符 | 后。
使用场景:
- 需要格式化数据,例如将价格加上单位显示 例如将数据拼接加上单位返回:
<span>{{ row | configText }}</span>
configText(item) {
return window.i18nTool.$t('{item.cpu}核/{item.memory}GB/{item.sysDisk}GB', {
'item.cpu': item.cpu,
'item.memory': item.memory,
'item.sysDisk': item.sysDisk
});
},
18、data为什么是一个函数而不是对象?
-
data如果是一个对象,当多个实例引用同一个对象时,最后指向同一个对象,一个改变,其他的也会改变
-
为了组件复用, 每个组件都有自己的数据,data是函数就拥有自己组件的私有数据,不会影响其他组件中的数据
19、vue单页应用与多页应用的区别?
-
SPA单页应用(SinglePage Web Application)只有一个主页面的应用,一个html,一开始只加载一次js,css等;所有的内容都包含在主页面中,模块化加载组件;切换组件仅刷新局部资源。
-
MPA多页应用(MultiPage Application),多个独立页面的应用,有多个html,也会执行多个js,css等;切换页面时会刷新整页资源。
SPA优点:
- 页面切换快,体验好
- 公用的资源只加载一次
- 局部刷新代码,代码可复用
SPA缺点:
- SEO搜索引擎难度高;
- 初次加载耗时多;
20、简述mixins、extends的覆盖逻辑?
1. mixins,extends
mixins,extends都是用于合并、扩展组件的,两者都可使用mergeOptions方法实现合并。
-
mixins选项接收一个混入对象的数组。这些混入对象可以像正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中。Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。 -
extends主要是为了扩展单文件组件,接受一个对象或构造函数。很少用到
data,provide的合并策略: mixins/extends只会将自己有的但是组件上没有的内容混合到组件上,重复的默认使用组件上的;如果data里的值是对象,将递归内部对象继续按照该策略合并
props,methods,inject,compouted,组件,过滤器,指令属性,el,prosData的合并策略: mixins/extends只会将自己有的但是组件上没有的内容混合到组件上
watch的合并策略: 合并watch监控的回调方法,执行顺序是先mixins/extends里watch定义的回调,然后是组件的回调
HOOKS生命周期钩子的合并策略: 同一种钩子的回调函数会被合并成数组,先mixins/extends中的钩子函数,再组件的
抽离相同代码baseInfo组件:
<script>
export default {
name: 'baseInfo',
computed: {
isApp() {
return this.systemType === 'app';
},
productAlias() {
return this.$route.query.productAlias;
},
productName() {
return this.$route.query.productName;
}
},
};
</script>
使用mixins:
<BreadcrumbItem>{{ productAlias }}</BreadcrumbItem>
import baseInfo from '../common/baseInfo';
export default {
mixins: [baseInfo],
}
2. mergeOptions执行过程:
-
规范化选项(normalizeProps、normalizelnject、normalizeDirectives)
-
对未合并的选项,进行判断,然后进行合并。根据一个通用 Vue 实例所包含的选项进行分类逐一判断合并,如 props、data、 methods、watch、computed、生命周期等,将合并结果存储在新定义的 options 对象里。
-
返回合并结果 options
21、什么是mixin?mixin与mixins的区别?
1. mixin
-
mixin用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。 -
Mixin 使我们能够为 Vue 组件编写可插拔和可重用的功能。
-
如果希望在多个组件之间重用一组组件选项,例如生命周期 hook、 方法等,则可以将其编写为 mixin,并在组件中简单的引用它。
-
然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。
2. mixin 与 mixins
mixin用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。mixins应该是最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过mixins混入代码,比如上拉下拉加载数据这种逻辑等等。 另外需要注意的是mixins混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并。