题目分析:vue是组件化开发框架,所以对于vue应用来说组件间的数据通信非常重要。此题主要考查大家vue基本功,对于vue基础api运用熟练度。另外一些边界知识如provide/inject/ a t t r s / attrs/ attrs/listeners则体现了面试者的知识面。
思路分析:总分
1.总述知道的所有方式
2.按组件关系阐述使用场景
回答范例:
组件通信方式大体有以下8种:
props
$emit/ $on
$children / $parent
$attrs/ $listeners
ref
$root
eventbus
vuex
(部分在V3已经移除)
根据组件之间关系讨论组件通信最为清晰有效
父子组件
props
$emit/ $on
$parent / $children
ref
$attrs / $listeners
兄弟组件
$parent
eventbus
vuex
跨层级关系
provide/inject
$root
eventbus
vuex
分析:
此题考查常识,文档中曾有详细说明v2|v3;也是一个很好的实践题目,项目中经常会遇到,能够看出面试者应用能力。
思路分析:总分总模式
先给出结论
为什么是这样的
它们能放一起吗
如果不能,那应该怎样
总结
回答范例:
在 Vue 2 中,v-for 优先于 v-if 被解析;但在 Vue 3 中,则完全相反,v-if 的优先级高于 v-for。
我曾经做过实验,把它们放在一起,输出的渲染函数中可以看出会先执行循环再判断条件
实践中也不应该把它们放一起,因为哪怕我们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表。
通常有两种情况下导致我们这样做:
为了过滤列表中的项目 (比如 v-for=“user in users” v-if=“user.isActive”)。此时定义一个计算属性 (比如 activeUsers),让其返回过滤后的列表即可。
为了避免渲染本应该被隐藏的列表 (比如 v-for=“user in users” v-if=“shouldShowUsers”)。此时把 v-if 移动至容器元素上 (比如 ul、ol)即可。
文档中明确指出永远不要把 v-if 和 v-for 同时用在同一个元素上,显然这是一个重要的注意事项。
看过源码里面关于代码生成的部分,
分析:
这是一道特别常见的问题,主要考查大家对虚拟DOM和patch细节的掌握程度,能够反映面试者理解层次。
思路分析:总分总模式
1.给出结论,key的作用是用于优化patch性能
2.key的必要性
3.实际使用方式
总结:可从源码层面描述一下vue如何判断两个节点是否相同
回答范例:
key的作用主要是为了更高效的更新虚拟DOM。
vue在patch过程中判断两个节点是否是相同节点是key是一个必要条件,渲染一组列表时,key往往是唯一标识,所以如果不定义key的话,vue只能认为比较的两个节点是同一个,哪怕它们实际上不是,这导致了频繁更新元素,使得整个patch过程比较低效,影响性能。
实际使用中在渲染一组列表时key必须设置,而且必须是唯一标识,应该避免使用数组索引作为key,这可能导致一些隐蔽的bug;vue中在使用相同标签元素过渡切换时,也会使用key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
从源码中可以知道,vue判断两个节点是否相同时主要判断两者的key和元素类型等,因此如果不设置key,它的值就是undefined,则可能永远认为这是两个相同节点,只能去做更新操作,这造成了大量的dom更新操作,明显是不可取的。
简述 Vue 的生命周期以及每个阶段做的事
必问题目,考查vue基础知识。
思路
给出概念
列举生命周期各阶段
阐述整体流程
结合实践
扩展:vue3变化
回答范例
1.每个Vue组件实例被创建后都会经过一系列初始化步骤,比如,它需要数据观测,模板编译,挂载实例到dom上,以及数据变化时更新dom。这个过程中会运行叫做生命周期钩子的函数,以便用户在特定阶段有机会添加他们自己的代码。
2.Vue生命周期总共可以分为8个阶段:创建前后, 载入前后, 更新前后, 销毁前后,以及一些特殊场景的生命周期。vue3中新增了三个用于调试和服务端渲染场景。
3.Vue生命周期流程图:
4.结合实践:
beforeCreate:通常用于插件开发中执行一些初始化任务
created:组件初始化完毕,可以访问各种数据,获取接口数据等
mounted:dom已创建,可用于获取访问数据和dom元素;访问子组件等。
beforeUpdate:此时view层还未更新,可用于获取更新前各种状态
updated:完成view层的更新,更新后,所有状态已是最新
beforeunmounted:实例被销毁前调用,可用于一些定时器或订阅的取消
unmounted:销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器
题目分析:
双向绑定是vue的特色之一,开发中必然会用到的知识点,然而此题还问了实现原理,升级为深度考查。
思路分析:
1.给出双绑定义
2.双绑带来的好处
3.在哪使用双绑
4.使用方式
5.扩展:使用细节、原理实现描述
回答范例:
1.vue中双向绑定是一个指令v-model,可以绑定一个动态值到视图,同时视图中变化能改变该值。v-model是语法糖,默认情况下相当于:value和@input。
2.使用v-model可以减少大量繁琐的事件处理代码,提高开发效率,代码可读性也更好
3.通常在表单项上使用v-model
4.原生的表单项可以直接使用v-model,自定义组件上如果要使用它需要在组件内绑定value并处理输入事件
5.我做过测试,输出包含v-model模板的组件渲染函数,发现它会被转换为value属性的绑定以及一个事件监听,事件回调函数中会做相应变量更新操作,这说明神奇魔法实际上是vue的编译器完成的。
可能的追问:
1.v-model和sync修饰符有什么区别
2.自定义组件使用v-model如果想要改变事件名或者属性名应该怎么做
这道题考查大家对vue异步更新队列的理解,有一定深度,如果能够很好回答此题,对面试效果有极大帮助。
答题思路:
1.nextTick是啥?下一个定义
2.为什么需要它呢?用异步更新队列实现原理解释
3.我再什么地方用它呢?抓抓头,想想你在平时开发中使用它的地方
4.下面介绍一下如何使用nextTick
5.最后能说出源码实现就会显得你格外优秀
先看看官方定义
Vue.nextTick( [callback, context] )
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
// 修改数据
vm.msg = ‘Hello’
// DOM 还没有更新
Vue.nextTick(function () {
// DOM 更新了
})
回答范例:
1.nextTick是Vue提供的一个全局API,由于vue的异步更新策略导致我们对数据的修改不会立刻体现在dom变化上,此时如果想要立即获取更新后的dom状态,就需要使用这个方法
2.Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。3.nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用。
所以当我们想在修改数据后立即看到dom执行结果就需要用到nextTick方法。
4.比如,我在干什么的时候就会使用nextTick,传一个回调函数进去,在里面执行dom操作即可。
5.我也有简单了解nextTick实现,它会在callbacks里面加入我们传入的函数,然后用timerFunc异步方式调用它们,首选的异步方式会是Promise。这让我明白了为什么可以在nextTick中看到dom操作结果。
两个重要API,反应应聘者熟练程度。
思路分析
1.先看两者定义,列举使用上的差异
2.列举使用场景上的差异,如何选择
3.使用细节、注意事项
4.vue3变化
回答范例
1.计算属性可以从组件数据派生出新数据,最常见的使用方式是设置一个函数,返回计算之后的结果,computed和methods的差异是它具备缓存性,如果依赖项不变时不会重新计算。侦听器可以侦测某个响应式数据的变化并执行副作用,常见用法是传递一个函数,执行副作用,watch没有返回值,但可以执行异步操作等复杂逻辑。
2.计算属性常用场景是简化行内模板中的复杂表达式,模板中出现太多逻辑会是模板变得臃肿不易维护。侦听器常用场景是状态变化之后做一些额外的DOM操作或者异步操作。选择采用何用方案时首先看是否需要派生出新值,基本能用计算属性实现的方式首选计算属性。
使用过程中有一些细节,比如计算属性也是可以传递对象,成为既可读又可写的计算属性。watch可以传递对象,设置deep、immediate等选项。
2.vue3中watch选项发生了一些变化,例如不再能侦测一个点操作符之外的字符串形式的表达式; reactivity API中新出现了watch、watchEffect可以完全替代目前的watch选项,且功能更加强大。
可能追问
watch会不会立即执行?
watch 和 watchEffect有什么差异
缓存组件使用keep-alive组件,这是一个非常常见且有用的优化手段,vue3中keep-alive有比较大的更新,能说的点比较多。
思路
1.缓存用keep-alive,它的作用与用法
2.使用细节,例如缓存指定/排除、结合router和transition
3.组件缓存后更新可以利用activated或者beforeRouteEnter
4.原理阐述
回答范例
1.开发中缓存组件使用keep-alive组件,keep-alive是vue内置组件,keep-alive包裹动态组件component时,会缓存不活动的组件实例,而不是销毁它们,这样在组件切换过程中将状态保留在内存中,防止重复渲染DOM。
<keep-alive>
<component :is="view"></component>
</keep-alive>
2.结合属性include和exclude可以明确指定缓存哪些组件或排除缓存指定组件。vue3中结合vue-router时变化较大,之前是keep-alive包裹router-view,现在需要反过来用router-view包裹keep-alive:
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component"></component>
</keep-alive>
</router-view>
3.缓存后如果要获取数据,解决方案可以有以下两种:
beforeRouteEnter:在有vue-router的项目,每次进入路由的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){
next(vm=>{
console.log(vm)
// 每次进入路由执行
vm.getData() // 获取数据
})
},
actived:在keep-alive缓存的组件被激活的时候,都会执行actived钩子
activated(){
this.getData() // 获取数据
},
keep-alive是一个通用组件,它内部定义了一个map,缓存创建过的组件实例,它返回的渲染函数内部会查找内嵌的component组件对应组件的vnode,如果该组件在map中存在就直接返回它。由于component的is属性是个响应式数据,因此只要它变化,keep-alive的render函数就会重新执行。
分析
这是一道API题,我们可能写的自定义指令少,但是我们用的多呀,多举几个例子就行。
体验
定义一个包含类似组件生命周期钩子的对象,钩子函数会接收指令挂钩的dom元素:
const focus = {
mounted: (el) => el.focus()
}
export default {
directives: {
// enables v-focus in template
focus
}
}
<input v-focus />
思路
1.定义
2.何时用
3.如何用
4.常用指令
5.vue3变化
回答范例
1.Vue有一组默认指令,比如v-model或v-for,同时Vue也允许用户注册自定义指令来扩展Vue能力
2.自定义指令主要完成一些可复用低层级DOM操作
3.使用自定义指令分为定义、注册和使用三步:
定义自定义指令有两种方式:对象和函数形式,前者类似组件定义,有各种生命周期;后者只会在mounted和updated时执行
注册自定义指令类似组件,可以使用app.directive()全局注册,使用{directives:{xxx}}局部注册
使用时在注册名称前加上v-即可,比如v-focus
4.我在项目中常用到一些自定义指令,例如:
复制粘贴 v-copy
长按 v-longpress
防抖 v-debounce
图片懒加载 v-lazy
按钮权限 v-premission
页面水印 v-waterMarker
拖拽指令 v-draggable
vue3中指令定义发生了比较大的变化,主要是钩子的名称保持和组件一致,这样开发人员容易记忆,不易犯错。另外在v3.2之后,可以在setup中以一个小写v开头方便的定义自定义指令,更简单了!
分析
v-once是Vue中内置指令,很有用的API,在优化方面经常会用到,不过小伙伴们平时可能容易忽略它。
体验
仅渲染元素和组件一次,并且跳过未来更新
Render the element and component once only, and skip future updates.
<!-- single element -->
<span v-once>This will never change: {{msg}}</span>
<!-- the element have children -->
<div v-once>
<h1>comment</h1>
<p>{{msg}}</p>
</div>
<!-- component -->
<my-component v-once :comment="msg"></my-component>
<!-- `v-for` directive -->
<ul>
<li v-for="i in list" v-once>{{i}}</li>
</ul>
思路
1.v-once是什么
2.什么时候使用
3.如何使用
4.扩展v-memo
5.探索原理
回答范例
1.v-once是vue的内置指令,作用是仅渲染指定组件或元素一次,并跳过未来对其更新。
2.如果我们有一些元素或者组件在初始化渲染之后不再需要变化,这种情况下适合使用v-once,这样哪怕这些数据变化,vue也会跳过更新,是一种代码优化手段。
3.我们只需要作用的组件或元素上加上v-once即可。
4.vue3.2之后,又增加了v-memo指令,可以有条件缓存部分模板并控制它们的更新,可以说控制力更强了。
5.编译器发现元素上面有v-once时,会将首次计算结果存入缓存对象,组件再次渲染时就会从缓存获取,从而避免再次计算。
知其所以然
下面例子使用了v-once:
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1 v-once>{{ msg }}</h1>
<input v-model="msg">
</template>
我们发现v-once出现后,编译器会缓存作用元素或组件,从而避免以后更新时重新计算这一部分:
// ...
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock(_Fragment, null, [
// 从缓存获取vnode
_cache[0] || (
_setBlockTracking(-1),
_cache[0] = _createElementVNode("h1", null, [
_createTextVNode(_toDisplayString(msg.value), 1 /* TEXT */)
]),
_setBlockTracking(1),
_cache[0]
),
// ...
分析
递归组件我们用的比较少,但是在Tree、Menu这类组件中会被用到。
体验
组件通过组件名称引用它自己,这种情况就是递归组件。
An SFC can implicitly refer to itself via its filename.
<template>
<li>
<div> {{ model.name }}</div>
<ul v-show="isOpen" v-if="isFolder">
<!-- 注意这里:组件递归渲染了它自己 -->
<TreeItem
class="item"
v-for="model in model.children"
:model="model">
</TreeItem>
</ul>
</li>
<script>
export default {
name: 'TreeItem',
// ...
}
</script>
思路
1.下定义
2.使用场景
3.使用细节
4.原理阐述
回答范例
1.如果某个组件通过组件名称引用它自己,这种情况就是递归组件。
2.实际开发中类似Tree、Menu这类组件,它们的节点往往包含子节点,子节点结构和父节点往往是相同的。这类组件的数据往往也是树形结构,这种都是使用递归组件的典型场景。
3.使用递归组件时,由于我们并未也不能在组件内部导入它自己,所以设置组件name属性,用来查找组件定义,如果使用SFC,则可以通过SFC文件名推断。组件内部通常也要有递归结束条件,比如model.children这样的判断。
4.查看生成渲染函数可知,递归组件查找时会传递一个布尔值给resolveComponent,这样实际获取的组件就是当前组件本身。
知其所以然
递归组件编译结果中,获取组件时会传递一个标识符 _resolveComponent(“Comp”, true)
const _component_Comp = _resolveComponent(“Comp”, true)
就是在传递maybeSelfReference
export function resolveComponent(
name: string,
maybeSelfReference?: boolean
): ConcreteComponent | string {
return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name
}
resolveAsset中最终返回的是组件自身:
if (!res && maybeSelfReference) {
// fallback to implicit self-reference
return Component
}