面试题合集VUE
生命周期
- beforeCreate:该阶段获取不到props和data中的数据,因为这些数据的初始化都在initState中。
- created:这时可以访问props和data中的数据,但是还未挂载,因此不会显示内容
- beforeMount:开始创建VDOM(虚拟DOM)
- mounted:将VDOM渲染为真实的DOM并渲染数据,组件中如果有子组件的话会递归挂载子组件,当所有子组件挂载完毕,才会执行根组件挂载钩子
- beforeUpdate:在数据更新前调用
- updated:在数据更新后调用
- 另外keep-alive拥有独立的生命周期,分别为activated和deactivated,使用keep-alive包裹的组件在切换时不会进行销毁而是缓存到内存中执行deactivated,命中缓存渲染后会执行actived钩子函数
- beforeDestroy:销毁组件前执行,适合移除事件。定时器等,如果有子组件会递归销毁子组件,所有子组件都销毁后才会执行destroyed
- destroyed:销毁组件时执行
组件通信
组件通信的几种情况:
- 父子组件通信
- 兄弟组件通信
- 跨多层级组件通信
- 任意组件
(1)父子通信
父组件通过props
传递数据给子组件,子组件通过emit
发送事件传递数据给父组件。这是最常用的父子通信实现办法。这种父子通信方式是典型的单向数据流,父组件通过props
传递数据,子组件不可直接修改props
,可以使用过计算属性,也可通过发送事件的方式告知父组件修改数据。VUE2.3以上版本可以使用$listeners
和.sync
两个属性。$listeners
会将父组件中的(不含.natvie
修饰器的)v-on
事件监听器传递给子组件,子组件可以通过访问$listeners
来自定义监听器。.sync
属性是语法糖:
<!--父组件中-->
<input :value.sync='value'>
<!--上述写法等同于-->
<input :value='value' @update:value='v => value=v'/>
<!--子组件中-->
<script>
this.$emit('update:value',1)
</script>
(2)兄弟组件通信
可以通过查找父组件中的子组件实现,即this.$parent.$children
,在VUE3中不建议使用$children
,可以使用$refs
替代,预先给子组件设置ref
属性,在父组件中可以使用this.$refs.设定名称
进行访问。
(3)跨多层次组件通信
可以使用provide/inject
//父组件中
export default {
data() {
return {
title: '这是父组件中的内容'
}
},
provide() {
return {
title: this.title
}
}
}
//孙子组件中
export default {
inject: ['title']
}
任意组件
可以使用Vuex(在中大型项目中使用)或者Event Bus(在中小型项目中陪着provide和inject使用)解决任意组件之间传递数据的问题
extend
该API很少会用到。extend
配合$mount
使用可以拓展组件
mixin和mixinsmixin
用于全局混入,会影响到每个组件实例,通常组件都是这样初始化的,文档不建议使用。
Vue.mixin({
beforeCreate() {
//...逻辑
//这种方式会影响到每个组件的beforeCreate钩子函数
}
})
Mixins是我们最常使用的拓展组件的方式,如果多个组件有相同的业务逻辑,就可以将这些逻辑剥离出来,通过mixins混入代码
export const myMixin = {
data() {
return {
num:1
}
},
created() {
this.hello()
},
methods: {
hello() {
console.log('hello from mixin')
}
}
}
//要混入代码的组件
<script>
import {myMixin} from '@/assets/mixin.js'
export default {
mixins:[myMixin]
}
</script>
tip:mixins混入的钩子函数会先入组件内的钩子函数执行,并且在遇到同名选项时也会有选择性的进行合并。
computed和watch区别computed
是计算属性,依赖其他属性计算值,并且其值有缓存,只有当计算值变化才会返回内容watch
监听到值的变化就会执行回调。
一般需要依赖别的属性来动态获得值的时候可以使用computed
,对于监听到值的变化需要做一些复杂逻辑的情况下可以使用watch
computed
和watch
都支持对象的写法
vm.$watch('obj', {
//深度遍历
deep: true,
//立即触发
immediate: true,
//handler: function(val,oldVal){}
})
var vm = new Vue({
data: {a: 1},
computed: {
aPlus: {
//this.aPlus时触发
get: function() {
return this.a+1
},
set: function(v) {
this.a = v-1
}
}
}
})
keep-alive有什么用
在进行组件切换时,可以保存一些组件的状态防止多次渲染,就可以使用keep-alive
包裹需要保存的组件
v-show和v-if的区别v-show
是在display:none
和display:block
之间切换,无论初始条件是什么都会渲染。v-if
的值为true时组件就不会被渲染,只有条件为true时才会被渲染。
组件中data什么时候可以使用对象
组件复用时所有组件实例都会共享data,如果data是对象的话,就会造成一个组件修改data会影响到其他所有组件,所以要将data写成函数,每次用到就调用一次函数获得新的数据
当我们使用new Vue()
时,data设置为函数或是对象都是可以的,因为使用该方式该组件不会复用,也就不存在共享data的情况了
响应式原理
Vue2中使用Object.defineProperty()
来实现数据响应式,通过该函数可以监听到set
和get
的事件
var data = {name: '张辉'}
observe(data)
let name = data.name
data.name = '渣渣辉'
function observe(obj) {
//判断类型
if(!obj || typeof obj != 'object') {
throw new TypeError('xxxx')
}
Object.keys.forEach(key => {
defineReactive(obj,key,obj[key])
})
}
function defineReactive(obj,key,val) {
//递归自属性
observe(val)
Object.defineProperty(obj,key,{
//可枚举
enumerable: true,
//可配置
configurable: true,
//自定义函数
get: function reactiveGetter() {
console.log('get value')
return val
},
set: function reactiveSetter(newVal) {
console.log('change value')
val = newVal
}
})
}
上述代码简单实现了如何监听数据的set
和get
事件。因为自定义函数一开始是不会执行的,只有先执行了依赖收集,才可以在属性更新时派发更新。
接着实现一个Dep
类,用于解耦属性的依赖收集和派发更新操作:
class Dep {
constructor() {
this.subs = []
}
//添加依赖
addSub(sub) {
this.subs.push(sub)
}
//更新
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
//全局属性,通过该属性配置Watcher
Dep.target = null
当需要依赖收集时调用addSub
,当需要派发更新的时候调用notify
Vue组件挂载时添加响应式的过程:
先对所有需要的属性调用Object.defineProperty()
,然后实例化Watcher
,传入组件更新的回调。在实例化过程中,会对模板中的属性进行求值,触发依赖收集。
触发依赖收集时的操作:
class Watcher {
constructor(obj,key,cb){
//Dep.target指向自己
//然后触发属性的getter添加监听
//最后将Dep.target置空
Dep.target = this
this.cb = cb
this.obj = obj
this.key = key
this.value = obj[key]
Dep.target = null
}
update() {
//获得新值
this.value = this.obj[this.key]
//调用update方法更新DOM
this.cb(this.value)
}
}
上述代码就是Watcher
的简单实现,在执行构造函数的时候将Dep.target
指向自身,从而使得收集到了对应的Watcher
,在派发更新时取出对应的Watcher
然后执行update
函数
之后需要对defineReactive
函数进行改造,在自定义函数中添加依赖收集和派发更新相关的代码:
function defineReactive(obj,key,val) {
//递归子属性
observe(val)
let dp = new Dep()
Object.defineProperty(obj,key,{
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log('get value')
//将Watcher添加到订阅
if(Dep.target) {
dp.addSub(Dep.target)
}
return val
},
set: function reactiveSetter(newVal) {
console.log('change value')
//执行watcher的update方法
dp.notify()
}
})
}
上述代码实现了一个简易的数据响应式,核心思路就是手动触发一次属性的getter来实现依赖收集。
Object.defineProperty的缺陷
如果通过下标方式修改数组数据或者给对象新增属性并不会触发组件的重新渲染,因为Object.defineProperty
不能拦截到这些操作,准确来说,对于数组而言,大部分操作都是拦截不到的,只是Vue内部通过重写函数的方式解决了该问题。
上一篇: 面试题1