Vue面试题
未经允许 禁止转载
1. v-if和v-show的区别
v-show是通过CSS的display来控制显示隐藏
v-if是真正的渲染和销毁,不是显示和隐藏
频繁切换用v-show,否则V-if
2. 为何在v-for中使用key
必须用key,且尽量不能是index或random
在diff算法中通过tag和key来判断是不是sameVnode,所以key是必要的
减少渲染次数,提高渲染性能
3. 描述vue组件生命周期(父子组件)
单组件:
BeforeCreate,created,BeforeMount,mounted,BeforeUpdate,updated,BeforeDestroy,destroyed
父子组件:
父beforeCreate -> 子beforeCreate
子created -> 父created
父beforeMount -> 子beforeMount
子mounted -> 父mounted
父beforeUpdate -> 子beforeUpdate
子updated -> 父updated
父beforeDestroy -> 子beforeDestroy
子destroyed -> 父destroyed
4. 常用的组件通讯
父子组件props和this.$emit
自定义事件event.off/event.$emit
vuex
5. vue的渲染流程
三个知识点:响应式、虚拟DOM、模板渲染
流程:rander函数触发data的getter、使data被监听起来,data变化之后会触发setter,通知watcher,然后re-rander生成vnode
6. 双向数据绑定 v-modle的实现原理
input元素的动态value变量和绑定事件的$event.target.value是同一个变量,data更新会触发re-render
7. 对MVVM的理解
M(Model)V(View)VM(ViewModel)数据驱动视图
M可以理解为DOMTree,V可以理解为data对象,VM可以理解为vue动态指令、监听等。
MVVM架构可以理解为用vue的监听和动态指令等对data对象进行修改,然后渲染到DOM
8. computed有什么特点
缓存,data不变则不会重新计算
合理使用computed会提高性能
9. 为何组件data必须是一个函数
每个vue实际上是一个类,在使用的时候会把这个类实例化,只有当data是一个函数的时候形成闭包,才不会影响到其他实例化的类的data。
10. ajax应该放在哪个生命周期
mounted。因为JS是单线程,ajax是异步获取数据。放在mounted之前没有实际意义,只会让逻辑更加混乱。
11. 如何将所有props传递给子组件
<User :props='$props' />
12. 如何自己实现v-model
<input type='text' :value='val' @input='$emit('change',$event.target.value' />
model: {
prop: val,
event: change
},
props:{
val: {
type: String,
default: ''
}
}
13. 多个组件有相同的逻辑,如何抽离
mixin
import 'myMixin' from './myMixin.js'
export default{
mixins: [myMixin],
}
在myMixin.js里抽离公共逻辑,在需要使用的组件里引用该mixin
mixin存在的问题
变量来源不明确,不利于阅读
多mixin可能会造成命名冲突
mixin和组件可能出现多对多的关系,复杂度较高
14. 何时要使用异步组件
加载大组件、路由异步加载
15. 何时使用keep-alive
缓存组件,不需要重复渲染
多个静态tab页的切换
16. 何时需要使用beforeDestory
解绑自定义事件event.$off
清除定时器
解绑自定义的DOM事件,如window scroll等
17. 什么是作用域插槽
组件的slot有自己的data,传到父组件中
18. vuex中action和mutation有何区别
action处理异步,mutation不可以
mutation做原子操作(一次处理)
action可以整合多个mutation(提交Mutation的集合)
19. vue-router常用的路由模式
hash(默认)
h5 history(需要服务端支持)
20. 如何配置vue-router异步加载
const router = new VueRouter({
path: '/',
component:=>import('./../components/xxx')
})
21. 用vnode描述一个DOM结构
<!-- DOM -->
<div class='container' id='div1'>
<p>vdom</p>
<ul style='font-size: 14px;'>
<li>a</li>
</ul>
</div>
//用JS模拟DOM
{
tag: 'div',
props:{
id: 'div1',
className: 'container'
},
children: [
{
tag: 'p',
children: 'vdom'
},
{
tag: 'ul',
props: {
style: 'font-size: 14px'
},
children: [
{
tag: 'li',
children: 'a'
}
]
}
]
}
22. 监听data变化的核心API是什么
Object.defineProperty
深度监听、监听数组
缺点:
- 深度监听需要递归到底,一次性计算量大。
- 无法监听新增/删除属性。(需要用Vue.set/Vue.delete)
- 无法监听数组
23. vue如何监听数组变化
Object.definedProperty不能监听数组变化
重新定义原型 重写pop push等方法 实现监听
vue3.0 proxy可以原生支持监听数组变化
24. 描述响应式原理
监听data变化 组件渲染和更新流程
25. diff算法的时间复杂度
O(n)
在O(n^3)的基础上通过以下方法达到O(n):
- 只比较同一层级,不跨级比较
- 如果tag不相同则直接销毁重建
- 如果tag和key相同,则认为是相同节点,不深入比较
26. 简述diff算法过程
patch(elem,vnode)、patch(vnode,newVnode)
patchVnode、addVnode、removeVnode
updateChildren(key的重要性)
27. vue为何是异步渲染,$nextTick有何用
异步渲染能提高渲染性能
data多出修改合并,一次提交。所以正常情况拿不到最新的DOM,需要通过$nextTick拿到最新DOM
28. vue常见性能优化
合理使用v-if v-show
合理使用computed
v-for中加key
v-for避免和v-if同时使用(因为v-for的优先级高,每次v-for都需要处理v-if)
自定义事件、DOM事件要及时销毁
合理使用异步组件
合理使用keep-alive
data层级不要太深(深度监听需要遍历到底,一次性计算量大)
使用vue-loader做预编译
29. vue3.0升级内容
全部使用TS重写
性能提升,代码量减少
调整部分API
30. vue3.0使用proxy重写响应式
Object.defineProperty的缺点:
- 深度监听需要递归到底,一次性计算量大。
- 无法监听新增/删除属性。(需要用Vue.set/Vue.delete)
- 无法监听数组
Proxy:
get、set、delete
Reflect作用:
和Proxy能力一一对应、规范化、标准化、函数式、代替Object上的工具函数(如: Reflect.ownKeys()替代Object.getOwnPropertyNames())
Proxy实现响应式原理:
const data = {
name: 'liu',
age: 25
}
const proxyData = reactive(data)
function reactive(target = {}){
//判断target是否是对象
if(target !== 'object' || target == null){
return target
}
//代理配置
const proxyConf = {
get(target,key,reciver){
const result = Reflect.get(target,key,reciver)
return result
},
set(target,key,value,reciver){
const result = Reflect.set(target,key,value,reciver)
return result
},
delete(target,key){
const result = Reflect.delete(target,key)
return result
}
}
//生成代理对象
const observer = new Proxy(target,proxyConf)
return observer
}
console.log(proxyData.name) //get liu
proxyData.name = 'zhangsan' //set zhangsan
delete proxyData.name //删除name