01-Vue组件之间通信⽅式有哪些
vue是组件化开发框架,所以对于vue应⽤来说组件间的数据通信⾮常重要。
此题主要考查⼤家vue基本功,对于vue基础api运⽤熟练度。
另外⼀些边界知识如provide/inject/$attrs则提现了⾯试者的知识⼴度
思路分析:
- 总述知道的所有⽅式
- 按组件关系阐述使⽤场景
回答范例: - 组件通信常⽤⽅式有以下8种:
props
on
parent
listeners
ref
$root
eventbus
vuex
注意vue3中废弃的⼏个API
. 根据组件之间关系讨论组件通信最为清晰有效
⽗⼦组件
props / parent / ref / $attrs
兄弟组件
root / eventbus / vuex
跨层级关系
eventbus / vuex / provide + inject
参考地址
https://v3-migration.vuejs.org/breaking-changes/children.html
https://v3-migration.vuejs.org/breaking-changes/listeners-removed.html
https://v3-migration.vuejs.org/breaking-changes/events-api.html#overview
02-v-if和v-for哪个优先级更⾼?
分析:
此题考查常识,⽂档中曾有详细说明v2|v3;也是⼀个很好的实践题⽬,项⽬中经常会遇到,能够看出⾯试者api熟
悉程度和应⽤能⼒。
思路分析:
- 先给出结论
- 为什么是这样的,说出细节
- 哪些场景可能导致我们这样做,该怎么处理
- 总结,拔⾼
回答范例:
- 实践中不应该把v-for和v-if放⼀起
- 在vue2中,v-for的优先级是⾼于v-if,把它们放在⼀起,输出的渲染函数中可以看出会先执⾏循环再判断条
件,哪怕我们只渲染列表中⼀⼩部分元素,也得在每次重渲染的时候遍历整个列表,这会⽐较浪费;另外需要
注意的是在vue3中则完全相反,v-if的优先级⾼于v-for,所以v-if执⾏时,它调⽤的变量还不存在,就会导致
异常 - 通常有两种情况下导致我们这样做:
为了过滤列表中的项⽬ (⽐如 v-for="user in users" v-if="user.isActive" )。此时定义⼀个计算属性 (⽐如 activeUsers ),让其返回过滤后的列表即可(⽐如
users.filter(u=>u.isActive) )。
为了避免渲染本应该被隐藏的列表 (⽐如 v-for="user in users" v-if="shouldShowUsers" )。此
时把 v-if 移动⾄容器元素上 (⽐如 ul 、 ol )或者外⾯包⼀层 template 即可。 - ⽂档中明确指出永远不要把 v-if 和 v-for 同时⽤在同⼀个元素上,显然这是⼀个重要的注意事项。
- 源码⾥⾯关于代码⽣成的部分,能够清晰的看到是先处理v-if还是v-for,顺序上vue2和vue3正好相反,因此
产⽣了⼀些症状的不同,但是不管怎样都是不能把它们写在⼀起的。
v2:https://github1s.com/vuejs/vue/blob/HEAD/src/compiler/codegen/index.js#L65-L66
v3:https://github1s.com/vuejs/core/blob/HEAD/packages/compiler-core/src/codegen.ts#L586-L587
03-简述 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:销毁⼀个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器
可能的追问
- setup和created谁先执⾏?
- setup中为什么没有beforeCreate和created?
知其所以然
vue3中⽣命周期的派发时刻:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/componentOptions.ts#L554-L555
vue2中声明周期的派发时刻:
https://github1s.com/vuejs/vue/blob/HEAD/src/core/instance/init.js#L55-L56
04-能说⼀说双向绑定使⽤和原理吗?
题⽬分析:
双向绑定是 vue 的特⾊之⼀,开发中必然会⽤到的知识点,然⽽此题还问了实现原理,升级为深度考查。
思路分析:
- 给出双绑定义
- 双绑带来的好处
- 在哪使⽤双绑
- 使⽤⽅式、使⽤细节、vue3变化
- 原理实现描述
回答范例:
- vue中双向绑定是⼀个指令 v-model ,可以绑定⼀个响应式数据到视图,同时视图中变化能改变该值。
- v-model 是语法糖,默认情况下相当于 :value 和 @input 。使⽤ v-model 可以减少⼤量繁琐的事件处理代
码,提⾼开发效率。 - 通常在表单项上使⽤ v-model ,还可以在⾃定义组件上使⽤,表示某个值的输⼊和输出控制。
- 通过 的⽅式将xxx的值绑定到表单元素value上;对于checkbox,可以使⽤ truevalue 和false-value指定特殊的值,对于radio可以使⽤value指定特殊的值;对于select可以通过options元
素的value设置特殊的值;还可以结合.lazy,.number,.trim对v-mode的⾏为做进⼀步限定; v-model ⽤在⾃
定义组件上时⼜会有很⼤不同,vue3中它类似于 sync 修饰符,最终展开的结果是modelValue属性和
update:modelValue事件;vue3中我们甚⾄可以⽤参数形式指定多个不同的绑定,例如v-model:foo和vmodel:bar,⾮常强⼤! - v-model 是⼀个指令,它的神奇魔法实际上是vue的编译器完成的。我做过测试,包含 v-model 的模板,转
换为渲染函数之后,实际上还是是value属性的绑定以及input事件监听,事件回调函数中会做相应变量更新操
作。编译器根据表单元素的不同会展开不同的DOM属性和事件对,⽐如text类型的input和textarea会展开为
value和input事件;checkbox和radio类型的input会展开为checked和change事件;select⽤value作为属
性,⽤change作为事件。
可能的追问:
- v-model 和 sync 修饰符有什么区别
- ⾃定义组件使⽤ v-model 如果想要改变事件名或者属性名应该怎么做
05-Vue中如何扩展⼀个组件
此题属于实践题,考察⼤家对vue常⽤api使⽤熟练度,答题时不仅要列出这些解决⽅案,同时最好说出他们异同。
答题思路:
- 按照逻辑扩展和内容扩展来列举,
逻辑扩展有:mixins、extends、composition api;
内容扩展有slots; - 分别说出他们使⽤⽅法、场景差异和问题。
- 作为扩展,还可以说说vue3中新引⼊的composition api带来的变化
回答范例:
- 常⻅的组件扩展⽅法有:mixins,slots,extends等
- 混⼊mixins是分发 Vue 组件中可复⽤功能的⾮常灵活的⽅式。混⼊对象可以包含任意组件选项。当组件使⽤
混⼊对象时,所有混⼊对象的选项将被混⼊该组件本身的选项。
// 复⽤代码:它是⼀个配置对象,选项和组件⾥⾯⼀样
const mymixin = {
methods: {
dosomething(){}
}
}
// 全局混⼊:将混⼊对象传⼊
Vue.mixin(mymixin)
// 局部混⼊:做数组项设置到mixins选项,仅作⽤于当前组件
const Comp = {
mixins: [mymixin]
}
- 插槽主要⽤于vue组件中的内容分发,也可以⽤于组件扩展。
⼦组件Child
这个内容会被⽗组件传递的内容替换
⽗组件Parent
来⾃⽼爹的内容
如果要精确分发到不同位置可以使⽤具名插槽,如果要使⽤⼦组件中的数据可以使⽤作⽤域插槽。
- 组件选项中还有⼀个不太常⽤的选项extends,也可以起到扩展组件的⽬的
// 扩展对象
const myextends = {
methods: {
dosomething(){}
}
}
// 组件扩展:做数组项设置到extends选项,仅作⽤于当前组件
// 跟混⼊的不同是它只能扩展单个对象
// 另外如果和混⼊发⽣冲突,该选项优先级较⾼,优先起作⽤
const Comp = {
extends: myextends
}
- 混⼊的数据和⽅法不能明确判断来源且可能和当前组件内变量产⽣命名冲突,vue3中引⼊的composition
api,可以很好解决这些问题,利⽤独⽴出来的响应式模块可以很⽅便的编写独⽴逻辑并提供响应式的数据,
然后在setup选项中组合使⽤,增强代码的可读性和维护性。例如:
// 复⽤逻辑1
function useXX() {}
// 复⽤逻辑2
function useYY() {}
// 逻辑组合
const Comp = {
setup() {
const {xx} = useXX()
const {yy} = useYY()
return {xx, yy}
}
}
可能的追问
Vue.extend⽅法你⽤过吗?它能⽤来做组件扩展吗?
知其所以然
mixins原理:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/apiCreateApp.ts#L232-L233
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/componentOptions.ts#L545
slots原理:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/componentSlots.ts#L129-L130
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1373-L1374
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/helpers/renderSlot.ts#L23-L24
06-⼦组件可以直接改变⽗组件的数据么,说明原因
分析
这是⼀个实践知识点,组件化开发过程中有个单项数据流原则,不在⼦组件中修改⽗组件是个常识问题。
参考⽂档:https://staging.vuejs.org/guide/components/props.html#one-way-data-flow
思路
- 讲讲单项数据流原则,表明为何不能这么做
- 举⼏个常⻅场景的例⼦说说解决⽅案
- 结合实践讲讲如果需要修改⽗组件状态应该如何做
回答范例
- 所有的 prop 都使得其⽗⼦之间形成了⼀个单向下⾏绑定:⽗级 prop 的更新会向下流动到⼦组件中,但是反
过来则不⾏。这样会防⽌从⼦组件意外变更⽗级组件的状态,从⽽导致你的应⽤的数据流向难以理解。另外,
每次⽗级组件发⽣变更时,⼦组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在⼀个⼦组件内
部改变 prop。如果你这样做了,Vue 会在浏览器控制台中发出警告。
const props = defineProps(['foo'])
//
下⾯⾏为会被警告, props是只读的!
props.foo = 'bar'
- 实际开发过程中有两个场景会想要修改⼀个属性:
这个 prop ⽤来传递⼀个初始值;这个⼦组件接下来希望将其作为⼀个本地的 prop 数据来使⽤。在这
种情况下,最好定义⼀个本地的 data,并将这个 prop ⽤作其初始值:
const props = defineProps(['initialCounter'])
const counter = ref(props.initialCounter)
这个 prop 以⼀种原始的值传⼊且需要进⾏转换。在这种情况下,最好使⽤这个 prop 的值来定义⼀个计
算属性:
const props = defineProps(['size'])
// prop变化,计算属性⾃动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())
- 实践中如果确实想要改变⽗组件属性应该emit⼀个事件让⽗组件去做这个变更。注意虽然我们不能直接修改
⼀个传⼊的对象或者数组类型的prop,但是我们还是能够直接改内嵌的对象或属性。
07-Vue要做权限管理该怎么做?控制到按钮级别的权限怎么做?
分析
综合实践题⽬,实际开发中经常需要⾯临权限管理的需求,考查实际应⽤能⼒。
权限管理⼀般需求是两个:⻚⾯权限和按钮权限,从这两个⽅⾯论述即可。
思路
- 权限管理需求分析:⻚⾯和按钮权限
- 权限管理的实现⽅案:分后端⽅案和前端⽅案阐述
- 说说各⾃的优缺点
回答范例
- 权限管理⼀般需求是⻚⾯权限和按钮权限的管理
- 具体实现的时候分后端和前端两种⽅案:
前端⽅案会把所有路由信息在前端配置,通过路由守卫要求⽤户登录,⽤户登录后根据⻆⾊过滤出路由表。⽐
如我会配置⼀个 asyncRoutes 数组,需要认证的⻚⾯在其路由的 meta 中添加⼀个 roles 字段,等获取⽤户
⻆⾊之后取两者的交集,若结果不为空则说明可以访问。此过滤过程结束,剩下的路由就是该⽤户能访问的⻚
⾯,最后通过 router.addRoutes(accessRoutes) ⽅式动态添加路由即可。
后端⽅案会把所有⻚⾯路由信息存在数据库中,⽤户登录的时候根据其⻆⾊查询得到其能访问的所有⻚⾯路由
信息返回给前端,前端再通过 addRoutes 动态添加路由信息
按钮权限的控制通常会实现⼀个指令,例如 v-permission ,将按钮要求⻆⾊通过值传给v-permission指
令,在指令的 moutned 钩⼦中可以判断当前⽤户⻆⾊和按钮是否存在交集,有则保留按钮,⽆则移除按钮。 - 纯前端⽅案的优点是实现简单,不需要额外权限管理⻚⾯,但是维护起来问题⽐较⼤,有新的⻚⾯和⻆⾊需求
就要修改前端代码重新打包部署;服务端⽅案就不存在这个问题,通过专⻔的⻆⾊和权限管理⻚⾯,配置⻚⾯
和按钮权限信息到数据库,应⽤每次登陆时获取的都是最新的路由信息,可谓⼀劳永逸!
知其所以然
路由守卫
https://github1s.com/PanJiaChen/vue-element-admin/blob/HEAD/src/permission.js#L13-L14
路由⽣成
https://github1s.com/PanJiaChen/vue-element-admin/blob/HEAD/src/store/modules/permission.js#L50-L51
动态追加路由
https://github1s.com/PanJiaChen/vue-element-admin/blob/HEAD/src/permission.js#L43-L44
可能的追问
- 类似 Tabs 这类组件能不能使⽤ v-permission 指令实现按钮权限控制?
⽤户管理
⻆⾊管理
- 服务端返回的路由信息如何添加到路由器中?
// 前端组件名和组件映射表
const map = {
//xx: require('@/views/xx.vue').default // 同步的⽅式
xx: () => import('@/views/xx.vue') // 异步的⽅式
}
// 服务端返回的asyncRoutes
const asyncRoutes = [
{ path: '/xx', component: 'xx',... }
]
// 遍历asyncRoutes,将component替换为map[component]
function mapComponent(asyncRoutes) {
asyncRoutes.forEach(route => {
route.component = map[route.component];
if(route.children) {
route.children.map(child => mapComponent(child))
}
})
}
mapComponent(asyncRoutes)
08 - 说⼀说你对vue响应式理解?
分析
这是⼀道必问题⽬,但能回答到位的⽐较少。如果只是看看⼀些⽹⽂,通常没什么底⽓,经不住⾯试官推敲,但像
我们这样即看过源码还造过轮⼦的,回答这个问题就会⽐较有底⽓啦。
答题思路:
- 啥是响应式?
- 为什么vue需要响应式?
- 它能给我们带来什么好处?
- vue的响应式是怎么实现的?有哪些优缺点?
- vue3中的响应式的新变化
回答范例:
- 所谓数据响应式就是能够使数据变化可以被检测并对这种变化做出响应的机制。
- MVVM框架中要解决的⼀个核⼼问题是连接数据层和视图层,通过数据驱动应⽤,数据变化,视图更新,要做
到这点的就需要对数据做响应式处理,这样⼀旦数据发⽣变化就可以⽴即做出更新处理。 - 以vue为例说明,通过数据响应式加上虚拟DOM和patch算法,开发⼈员只需要操作数据,关⼼业务,完全不
⽤接触繁琐的DOM操作,从⽽⼤⼤提升开发效率,降低开发难度。 - vue2中的数据响应式会根据数据类型来做不同处理,如果是对象则采⽤Object.defineProperty()的⽅式定
义数据拦截,当数据被访问或发⽣变化时,我们感知并作出响应;如果是数组则通过覆盖数组对象原型的7个
变更⽅法,使这些⽅法可以额外的做更新通知,从⽽作出响应。这种机制很好的解决了数据响应化的问题,但
在实际使⽤中也存在⼀些缺点:⽐如初始化时的递归遍历会造成性能损失;新增或删除属性时需要⽤户使⽤
Vue.set/delete这样特殊的api才能⽣效;对于es6中新产⽣的Map、Set这些数据结构不⽀持等问题。 - 为了解决这些问题,vue3重新编写了这⼀部分的实现:利⽤ES6的Proxy代理要响应化的数据,它有很多好
处,编程体验是⼀致的,不需要使⽤特殊api,初始化性能和内存消耗都得到了⼤幅改善;另外由于响应化的
实现代码抽取为独⽴的reactivity包,使得我们可以更灵活的使⽤它,第三⽅的扩展开发起来更加灵活了。
知其所以然
vue2响应式:
https://github1s.com/vuejs/vue/blob/HEAD/src/core/observer/index.js#L135-L136
vue3响应式:
https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/reactive.ts#L89-L90
https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/ref.ts#L67-L68
09 - 说说你对虚拟 DOM 的理解?
分析
现有框架⼏乎都引⼊了虚拟 DOM 来对真实 DOM 进⾏抽象,也就是现在⼤家所熟知的 VNode 和 VDOM,那么为
什么需要引⼊虚拟 DOM 呢?围绕这个疑问来解答即可!
思路
- vdom是什么
- 引⼊vdom的好处
- vdom如何⽣成,⼜如何成为dom
- 在后续的diff中的作⽤
回答范例
- 虚拟dom顾名思义就是虚拟的dom对象,它本身就是⼀个 JavaScript 对象,只不过它是通过不同的属性去
描述⼀个视图结构。 - 通过引⼊vdom我们可以获得如下好处:
将真实元素节点抽象成 VNode,有效减少直接操作 dom 次数,从⽽提⾼程序性能
直接操作 dom 是有限制的,⽐如:diff、clone 等操作,⼀个真实元素上有许多的内容,如果直接对其
进⾏ diff 操作,会去额外 diff ⼀些没有必要的内容;同样的,如果需要进⾏ clone 那么需要将其全部内
容进⾏复制,这也是没必要的。但是,如果将这些操作转移到 JavaScript 对象上,那么就会变得简单
了。
操作 dom 是⽐较昂贵的操作,频繁的dom操作容易引起⻚⾯的重绘和回流,但是通过抽象 VNode 进⾏
中间处理,可以有效减少直接操作dom的次数,从⽽减少⻚⾯重绘和回流。
⽅便实现跨平台
同⼀ VNode 节点可以渲染成不同平台上的对应的内容,⽐如:渲染在浏览器是 dom 元素节点,渲染在
Native( iOS、Android) 变为对应的控件、可以实现 SSR 、渲染到 WebGL 中等等
Vue3 中允许开发者基于 VNode 实现⾃定义渲染器(renderer),以便于针对不同平台进⾏渲染。 -
vdom如何⽣成?在vue中我们常常会为组件编写模板 - template, 这个模板会被编译器 - compiler编译为渲
染函数,在接下来的挂载(mount)过程中会调⽤render函数,返回的对象就是虚拟dom。但它们还不是真
正的dom,所以会在后续的patch过程中进⼀步转化为dom。
- 挂载过程结束后,vue程序进⼊更新流程。如果某些响应式数据发⽣变化,将会引起组件重新render,此时就
会⽣成新的vdom,和上⼀次的渲染结果diff就能得到变化的地⽅,从⽽转换为最⼩量的dom操作,⾼效更新
视图。
知其所以然
vnode定义:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/vnode.ts#L127-L128
观察渲染函数:21-vdom/test-render-v3.html
创建vnode:
createElementBlock:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/vnode.ts#L291-L292
createVnode:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/vnode.ts#L486-L487
⾸次调⽤时刻:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/apiCreateApp.ts#L283-L284
mount:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1171-L1172
调试mount过程:mountComponent
10 - 你了解diff算法吗?
分析
必问题⽬,涉及vue更新原理,⽐较考查理解深度
10 - 你了解diff算法吗?
分析
必问题⽬,涉及vue更新原理,⽐较考查理解深度
思路
- diff算法是⼲什么的
- 它的必要性
- 它何时执⾏
- 具体执⾏⽅式
- 拔⾼:说⼀下vue3中的优化
回答范例
1.Vue中的diff算法称为patching算法,它由Snabbdom修改⽽来,虚拟DOM要想转化为真实DOM就需要通过
patch⽅法转换。
2.最初Vue1.x视图中每个依赖均有更新函数对应,可以做到精准更新,因此并不需要虚拟DOM和patching算法⽀
持,但是这样粒度过细导致Vue1.x⽆法承载较⼤应⽤;Vue 2.x中为了降低Watcher粒度,每个组件只有⼀个
Watcher与之对应,此时就需要引⼊patching算法才能精确找到发⽣变化的地⽅并⾼效更新。
3.vue中diff执⾏的时刻是组件内响应式数据变更触发实例执⾏其更新函数时,更新函数会再次执⾏render函数获
得最新的虚拟DOM,然后执⾏patch函数,并传⼊新旧两次虚拟DOM,通过⽐对两者找到变化的地⽅,最后将其
转化为对应的DOM操作。
4.patch过程是⼀个递归过程,遵循深度优先、同层⽐较的策略;以vue3的patch为例:
⾸先判断两个节点是否为相同同类节点,不同则删除重新创建
如果双⽅都是⽂本则更新⽂本内容
如果双⽅都是元素节点则递归更新⼦元素,同时更新元素属性
更新⼦节点时⼜分了⼏种情况:
新的⼦节点是⽂本,⽼的⼦节点是数组则清空,并设置⽂本;
新的⼦节点是⽂本,⽼的⼦节点是⽂本则直接更新⽂本;
新的⼦节点是数组,⽼的⼦节点是⽂本则清空⽂本,并创建新⼦节点数组中的⼦元素;
新的⼦节点是数组,⽼的⼦节点也是数组,那么⽐较两组⼦节点,更新细节blabla
- vue3中引⼊的更新策略:编译期优化patchFlags、block等
知其所以然
patch关键代码
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L354-L355
11 - 你知道哪些vue3新特性
分析
官⽹列举的最值得注意的新特性:https://v3-migration.vuejs.org/
也就是下⾯这些:
- Composition API
- SFC Composition API语法糖
- Teleport传送⻔
- Fragments⽚段
- Emits选项
- ⾃定义渲染器
- SFC CSS变量
- Suspense
以上这些是api相关,另外还有很多框架特性也不能落掉。
回答范例
- api层⾯Vue3新特性主要包括:Composition API、SFC Composition API语法糖、Teleport传送⻔、
Fragments ⽚段、Emits选项、⾃定义渲染器、SFC CSS变量、Suspense - 另外,Vue3.0在框架层⾯也有很多亮眼的改进:
更快
虚拟DOM重写
编译器优化:静态提升、patchFlags、block等
基于Proxy的响应式系统
更⼩:更好的摇树优化
更容易维护:TypeScript + 模块化
更容易扩展
独⽴的响应化模块
⾃定义渲染器
知其所以然
体验编译器优化
https://sfc.vuejs.org/
reactive实现
https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/reactive.ts#L90-L91
12 - 怎么定义动态路由?怎么获取传过来的动态参数?
分析
API题⽬,考查基础能⼒,不容有失,尽可能说的详细。
思路
- 什么是动态路由
- 什么时候使⽤动态路由,怎么定义动态路由
- 参数如何获取
- 细节、注意事项
回答范例
- 很多时候,我们需要将给定匹配模式的路由映射到同⼀个组件,这种情况就需要定义动态路由。
- 例如,我们可能有⼀个 User 组件,它应该对所有⽤户进⾏渲染,但⽤户 ID 不同。在 Vue Router 中,我们
可以在路径中使⽤⼀个动态字段来实现,例如: { path: '/users/:id', component: User } ,其中
:id 就是路径参数 - 路径参数 ⽤冒号 : 表示。当⼀个路由被匹配时,它的 params 的值将在每个组件中以
this.$route.params 的形式暴露出来。 - 参数还可以有多个,例如 /users/:username/posts/:postId ;除了 route 对
象还公开了其他有⽤的信息,如 route.hash 等。
可能的追问
- 如何响应动态路由参数的变化
https://router.vuejs.org/zh/guide/essentials/dynamic-matching.html#%E5%93%8D%E5%BA%94%E8%B7%A
F%E7%94%B1%E5%8F%82%E6%95%B0%E7%9A%84%E5%8F%98%E5%8C%96 - 我们如何处理404 Not Found路由
https://router.vuejs.org/zh/guide/essentials/dynamic-matching.html#%E6%8D%95%E8%8E%B7%E6%89%80
%E6%9C%89%E8%B7%AF%E7%94%B1%E6%88%96-404-not-found-%E8%B7%AF%E7%94%B1
13-如果让你从零开始写⼀个vue路由,说说你的思路
思路分析:
⾸先思考vue路由要解决的问题:⽤户点击跳转链接内容切换,⻚⾯不刷新。
借助hash或者history api实现url跳转⻚⾯不刷新
同时监听hashchange事件或者popstate事件处理跳转
根据hash值或者state值从routes表中匹配对应component并渲染之
回答范例:
⼀个SPA应⽤的路由需要解决的问题是⻚⾯跳转内容改变同时不刷新,同时路由还需要以插件形式存在,所以:
- ⾸先我会定义⼀个 createRouter 函数,返回路由器实例,实例内部做⼏件事:
保存⽤户传⼊的配置项
监听hash或者popstate事件
回调⾥根据path匹配对应路由 - 将router定义成⼀个Vue插件,即实现install⽅法,内部做两件事:
实现两个全局组件:router-link和router-view,分别实现⻚⾯跳转和内容显示
定义两个全局变量:router,组件内可以访问当前路由和路由器实例
知其所以然:
createRouter如何创建实例
https://github1s.com/vuejs/router/blob/HEAD/src/router.ts#L355-L356
事件监听
https://github1s.com/vuejs/router/blob/HEAD/src/history/html5.ts#L314-L315
RouterView
⻚⾯跳转RouterLink
https://github1s.com/vuejs/router/blob/HEAD/src/RouterLink.ts#L184-L185
内容显示RouterView
https://github1s.com/vuejs/router/blob/HEAD/src/RouterView.ts#L43-L44
14-能说说key的作⽤吗?
分析:
这是⼀道特别常⻅的问题,主要考查⼤家对虚拟DOM和patch细节的掌握程度,能够反映⾯试者理解层次。
思路分析:
- 给出结论,key的作⽤是⽤于优化patch性能
- key的必要性
- 实际使⽤⽅式
- 总结:可从源码层⾯描述⼀下vue如何判断两个节点是否相同
回答范例: - key的作⽤主要是为了更⾼效的更新虚拟DOM。
- vue在patch过程中判断两个节点是否是相同节点是key是⼀个必要条件,渲染⼀组列表时,key往往是唯⼀标
识,所以如果不定义key的话,vue只能认为⽐较的两个节点是同⼀个,哪怕它们实际上不是,这导致了频繁
更新元素,使得整个patch过程⽐较低效,影响性能。 - 实际使⽤中在渲染⼀组列表时key必须设置,⽽且必须是唯⼀标识,应该避免使⽤数组索引作为key,这可能
导致⼀些隐蔽的bug;vue中在使⽤相同标签元素过渡切换时,也会使⽤key属性,其⽬的也是为了让vue可以
区分它们,否则vue只会替换其内部属性⽽不会触发过渡效果。 - 从源码中可以知道,vue判断两个节点是否相同时主要判断两者的key和元素类型等,因此如果不设置key,
它的值就是undefined,则可能永远认为这是两个相同节点,只能去做更新操作,这造成了⼤量的dom更新
操作,明显是不可取的。
知其所以然
测试代码,test-v3.html
上⾯案例重现的是以下过程
判断是否为相同节点
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/vnode.ts#L342-L343
更新时的处理
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1752-L1753
源码中找答案:
判断是否为相同节点
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/vnode.ts#L342-L343
更新时的处理
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1752-L1753
15-说说nextTick的使⽤和原理?
分析
这道题及考察使⽤,有考察原理,nextTick在开发过程中应⽤的也较少,原理上和vue异步更新有密切关系,对于
⾯试者考查很有区分度,如果能够很好回答此题,对⾯试效果有极⼤帮助。
答题思路
- nextTick是做什么的?
- 为什么需要它呢?
- 开发时何时使⽤它?抓抓头,想想你在平时开发中使⽤它的地⽅
- 下⾯介绍⼀下如何使⽤nextTick
- 原理解读,结合异步更新和nextTick⽣效⽅式,会显得你格外优秀
回答范例:
- nextTick是⽤于获取下次DOM更新刷新的使⽤函数。
- Vue有个异步更新策略,意思是如果数据变化,Vue不会⽴刻更新DOM,⽽是开启⼀个队列,把组件更新函
数保存在队列中,在同⼀事件循环中发⽣的所有数据变更会异步的批量更新。这⼀策略导致我们对数据的修
改不会⽴刻体现在DOM上,此时如果想要获取更新后的DOM状态,就需要使⽤nextTick。 - 开发时,⽐如我希望获取列表更新后的⾼度就可以通过nextTick实现。
- nextTick签名如下: function nextTick(callback?: () => void): Promise
所以我们只需要在传⼊的回调函数中访问最新DOM状态即可,或者我们可以await nextTick⽅法返回的
Promise之后做这件事。 - 在Vue内部,nextTick之所以能够让我们看到DOM更新后的结果,是因为我们传⼊的callback会被添加到队列
刷新函数(flushSchedulerQueue)的后⾯,这样等队列内部的更新函数都执⾏之后,所有DOM操作也就结束
了,callback⾃然能够获取到最新的DOM值。
知其所以然:
- 源码解读:
更新函数⼊队:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1547-L1548
⼊队函数:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/scheduler.ts#L84-L85
nextTick定义:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/scheduler.ts#L58-L59 - 测试案例,test-v3.html
16-watch和computed的区别以及选择?
两个重要API,反应应聘者熟练程度。
思路分析
- 先看两者定义,列举使⽤上的差异
- 列举使⽤场景上的差异,如何选择
- 使⽤细节、注意事项
- vue3变化
回答范例
- 计算属性可以从组件数据派⽣出新数据,最常⻅的使⽤⽅式是设置⼀个函数,返回计算之后的结果,
computed和methods的差异是它具备缓存性,如果依赖项不变时不会重新计算。侦听器可以侦测某个响应
式数据的变化并执⾏副作⽤,常⻅⽤法是传递⼀个函数,执⾏副作⽤,watch没有返回值,但可以执⾏异步操
作等复杂逻辑。 - 计算属性常⽤场景是简化⾏内模板中的复杂表达式,模板中出现太多逻辑会是模板变得臃肿不易维护。侦听
器常⽤场景是状态变化之后做⼀些额外的DOM操作或者异步操作。选择采⽤何⽤⽅案时⾸先看是否需要派⽣
出新值,基本能⽤计算属性实现的⽅式⾸选计算属性。 - 使⽤过程中有⼀些细节,⽐如计算属性也是可以传递对象,成为既可读⼜可写的计算属性。watch可以传递对
象,设置deep、immediate等选项。 - vue3中watch选项发⽣了⼀些变化,例如不再能侦测⼀个点操作符之外的字符串形式的表达式; reactivity
API中新出现了watch、watchEffect可以完全替代⽬前的watch选项,且功能更加强⼤。
可能追问
- watch会不会⽴即执⾏?
- watch 和 watchEffect有什么差异
知其所以然
computed的实现
https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/computed.ts#L79-L80
ComputedRefImpl
https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/computed.ts#L26-L27
缓存性
https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/computed.ts#L59-L60
https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/computed.ts#L45-L46
watch的实现
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/apiWatch.ts#L158-L159
17-说⼀下 Vue ⼦组件和⽗组件创建和挂载顺序
这题考查⼤家对创建过程的理解程度。
思路分析
- 给结论
- 阐述理由
回答范例
- 创建过程⾃上⽽下,挂载过程⾃下⽽上;即:
parent created
child created
child mounted
parent mounted - 之所以会这样是因为Vue创建过程是⼀个递归过程,先创建⽗组件,有⼦组件就会创建⼦组件,因此创建时现
有⽗组件再有⼦组件;⼦组件⾸次创建时会添加mounted钩⼦到队列,等到patch结束在执⾏它们,可⻅⼦
组件的mounted钩⼦是先进⼊到队列中的,因此等到patch结束执⾏这些钩⼦时也先执⾏。
知其所以然
观察beforeCreated和created钩⼦的处理
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/componentOptions.ts#L554-L555
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/componentOptions.ts#L741-L742
观察beforeMount和mounted钩⼦的处理
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1310-L1311
测试代码,test-v3.html
18-怎么缓存当前的组件?缓存后怎么更新?
缓存组件使⽤keep-alive组件,这是⼀个⾮常常⻅且有⽤的优化⼿段,vue3中keep-alive有⽐较⼤的更新,能说的
点⽐较多。
思路
- 缓存⽤keep-alive,它的作⽤与⽤法
- 使⽤细节,例如缓存指定/排除、结合router和transition
- 组件缓存后更新可以利⽤activated或者beforeRouteEnter
- 原理阐述
回答范例
- 开发中缓存组件使⽤keep-alive组件,keep-alive是vue内置组件,keep-alive包裹动态组件component时,
会缓存不活动的组件实例,⽽不是销毁它们,这样在组件切换过程中将状态保留在内存中,防⽌重复渲染
DOM。
- 结合属性include和exclude可以明确指定缓存哪些组件或排除缓存指定组件。vue3中结合vue-router时变化
较⼤,之前是 keep-alive 包裹 router-view ,现在需要反过来⽤ router-view 包裹 keep-alive :
- 缓存后如果要获取数据,解决⽅案可以有以下两种:
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函数就会重新执⾏。
知其所以然
KeepAlive定义
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/components/KeepAlive.ts#L73-L74
缓存定义
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/components/KeepAlive.ts#L102-L1
03
缓存组件
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/components/KeepAlive.ts#L215-L2
16
获取缓存组件
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/components/KeepAlive.ts#L241-L2
42
测试缓存特性,test-v3.html
19-从0到1⾃⼰构架⼀个vue项⽬,说说有哪些步骤、哪些重要
插件、⽬录结构你会怎么组织
综合实践类题⽬,考查实战能⼒。没有什么绝对的正确答案,把平时⼯作的重点有条理的描述⼀下即可。
思路
- 构建项⽬,创建项⽬基本结构
- 引⼊必要的插件:
- 代码规范:prettier,eslint
- 提交规范:husky,lint-staged
- 其他常⽤:svg-loader,vueuse,nprogress
- 常⻅⽬录结构
回答范例
- 从0创建⼀个项⽬我⼤致会做以下事情:项⽬构建、引⼊必要插件、代码规范、提交规范、常⽤库和组件
- ⽬前vue3项⽬我会⽤vite或者create-vue创建项⽬
- 接下来引⼊必要插件:路由插件vue-router、状态管理vuex/pinia、ui库我⽐较喜欢element-plus和antdvue、http⼯具我会选axios
- 其他⽐较常⽤的库有vueuse,nprogress,图标可以使⽤vite-svg-loader
- 下⾯是代码规范:结合prettier和eslint即可
- 最后是提交规范,可以使⽤husky,lint-staged,commitlint
- ⽬录结构我有如下习惯:
.vscode :⽤来放项⽬中的 vscode 配置
plugins :⽤来放 vite 插件的 plugin 配置
public :⽤来放⼀些诸如 ⻚头icon 之类的公共⽂件,会被打包到dist根⽬录下
src :⽤来放项⽬代码⽂件
api :⽤来放http的⼀些接⼝配置
assets :⽤来放⼀些 CSS 之类的静态资源
components :⽤来放项⽬通⽤组件
layout :⽤来放项⽬的布局
router :⽤来放项⽬的路由配置
store :⽤来放状态管理Pinia的配置
utils :⽤来放项⽬中的⼯具⽅法类
views :⽤来放项⽬的⻚⾯⽂件
20-实际⼯作中,你总结的vue最佳实践有哪些?
看到这样的题⽬,可以⽤以下图⽚来回答:
思路
查看vue官⽅⽂档:
⻛格指南:https://vuejs.org/style-guide/
性能:https://vuejs.org/guide/best-practices/performance.html#overview
安全:https://vuejs.org/guide/best-practices/security.html
访问性:https://vuejs.org/guide/best-practices/accessibility.html
发布:https://vuejs.org/guide/best-practices/production-deployment.html
回答范例
我从编码⻛格、性能、安全等⽅⾯说⼏条:
- 编码⻛格⽅⾯:
命名组件时使⽤“多词”⻛格避免和HTML元素冲突
使⽤“细节化”⽅式定义属性⽽不是只有⼀个属性名
属性名声明时使⽤“驼峰命名”,模板或jsx中使⽤“⾁串命名”
使⽤v-for时务必加上key,且不要跟v-if写在⼀起 - 性能⽅⾯:
路由懒加载减少应⽤尺⼨
利⽤SSR减少⾸屏加载时间
利⽤v-once渲染那些不需要更新的内容
⼀些⻓列表可以利⽤虚拟滚动技术避免内存过度占⽤
对于深层嵌套对象的⼤数组可以使⽤shallowRef或shallowReactive降低开销
避免不必要的组件抽象 - 安全:
不使⽤不可信模板,例如使⽤⽤户输⼊拼接模板: template:+ userProvidedString +
⼩⼼使⽤v-html,:url,:style等,避免html、url、样式等注⼊ - 等等......
21 - 简单说⼀说你对vuex理解?
views -> Actions -> State
思路
- 给定义
- 必要性阐述
- 何时使⽤
- 拓展:⼀些个⼈思考、实践经验等
范例
- Vuex 是⼀个专为 Vue.js 应⽤开发的状态管理模式 + 库。它采⽤集中式存储,管理应⽤的所有组件的状态,
并以相应的规则保证状态以⼀种可预测的⽅式发⽣变化。 - 我们期待以⼀种简单的“单向数据流”的⽅式管理应⽤,即状态 -> 视图 -> 操作单向循环的⽅式。但当我们的应
⽤遇到多个组件共享状态时,⽐如:多个视图依赖于同⼀状态或者来⾃不同视图的⾏为需要变更同⼀状态。
此时单向数据流的简洁性很容易被破坏。因此,我们有必要把组件的共享状态抽取出来,以⼀个全局单例模
式管理。通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独⽴性,我们的代码将
会变得更结构化且易维护。这是vuex存在的必要性,它和react⽣态中的redux之类是⼀个概念。 - Vuex 解决状态管理的同时引⼊了不少概念:例如state、mutation、action等,是否需要引⼊还需要根据应
⽤的实际情况衡量⼀下:如果不打算开发⼤型单⻚应⽤,使⽤ Vuex 反⽽是繁琐冗余的,⼀个简单的 store 模
式就⾜够了。但是,如果要构建⼀个中⼤型单⻚应⽤,Vuex 基本是标配。 - 我在使⽤vuex过程中感受到⼀些blabla
可能的追问
- vuex有什么缺点吗?你在开发过程中有遇到什么问题吗?
- action和mutation的区别是什么?为什么要区分它们?
22-说说从 template 到 render 处理过程
分析
问我们template到render过程,其实是问vue 编译器 ⼯作原理。
思路
- 引⼊vue编译器概念
- 说明编译器的必要性
- 阐述编译器⼯作流程
回答范例
- Vue中有个独特的编译器模块,称为“compiler”,它的主要作⽤是将⽤户编写的template编译为js中可执⾏的
render函数。 - 之所以需要这个编译过程是为了便于前端程序员能⾼效的编写视图模板。相⽐⽽⾔,我们还是更愿意⽤HTML
来编写视图,直观且⾼效。⼿写render函数不仅效率底下,⽽且失去了编译期的优化能⼒。 - 在Vue中编译器会先对template进⾏解析,这⼀步称为parse,结束之后会得到⼀个JS对象,我们成为抽象语
法树AST,然后是对AST进⾏深加⼯的转换过程,这⼀步成为transform,最后将前⾯得到的AST⽣成为JS代
码,也就是render函数。
知其所以然
vue3编译过程窥探:
https://github1s.com/vuejs/core/blob/HEAD/packages/compiler-core/src/compile.ts#L61-L62
可能的追问
- Vue中编译器何时执⾏?
- react有没有编译器?
23-Vue实例挂载的过程中发⽣了什么?
分析
挂载过程完成了最重要的两件事:
- 初始化
- 建⽴更新机制
把这两件事说清楚即可!
回答范例
- 挂载过程指的是app.mount()过程,这个过程中整体上做了两件事:初始化和建⽴更新机制
- 初始化会创建组件实例、初始化组件状态,创建各种响应式数据
- 建⽴更新机制这⼀步会⽴即执⾏⼀次组件更新函数,这会⾸次执⾏组件渲染函数并执⾏patch将前⾯获得
vnode转换为dom;同时⾸次执⾏渲染函数会创建它内部响应式数据之间和组件更新函数之间的依赖关系,
这使得以后数据变化时会执⾏对应的更新函数。
知其所以然
mount函数定义
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/apiCreateApp.ts#L277-L278
⾸次render过程
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L2303-L2304
可能的追问
- 响应式数据怎么创建
- 依赖关系如何建⽴