总结了一下vue3相关的面试题,方便复习。选题原则:剔除了即将过时的内容,比如option API、getCurrentInstance()
、setup()
函数形式的写法、mixin代码复用、通过app.config.globalProperties
注册全局变量。
目录
- vue3的编译流程?
- vue3内的宏是什么?
<script setup/>
的实现原理?- 组件实例是什么?
- 启动程序的时候,vue3单文件组件的初始化流程?
createApp()
,mount()
做了什么工作?- 组件的生命周期?
- 什么是虚拟DOM?diff算法?就地复用?
- 任务调度系统?
- 异步刷新是什么?怎么实现的?
nextTick()
的实现原理?- 有哪三种
Effect
?作用? watch()
,watchEffect()
,computed()
的区别?- 响应式编程的实现原理?
ref()
和reactive()
的区别?ref()
和ref(null)
的区别?attrs
和props
的区别?provide
和inject
实现原理?emit()
的实现原理?- 单向数据流的原因?
- 模板的编译流程?在什么时候编译?
- 组件的注册是什么?
- 指令的实现原理?
- 各种指令的区别和作用?
v-for
为什么要加key?v-for
为什么不能和v-if
一起使用?v-model
的实现原理?- hook是什么?
- 插件是什么?实现原理?
- 什么是异步组件?实现原理?
- 前端路由是什么?什么是路由懒加载?
问题
(1)vue3编译流程?
vue3的编译分两部分:编译期和运行期。编译期间,先通过vue-loader把单文件组件编译成符合原生JavaScript语法的模块;运行期间,会通过内置的模板引擎对模板进行编译,生成
render()
函数,且按照先父组件后子组件的顺序对模板进行编译。
(2)vue3内的宏是什么?和语法糖的区别?
按照vue3官方的说法,defineProps()
、defineEmits()
、defineExpose()
都是宏,它们使用形式上和函数调用差不多,区别是,第一,这些宏不需要通过模块导入;第二,这些宏会在编译期被编译成符合JavaScript原生语法的代码。语法糖也会被编译成符合JavaScript原生语法的代码,vue3官方对这两者进行区分是因为,语法糖创建的是不曾有过的语法,而宏指的是给现有语法新的含义。
(3)<script setup/>
的实现原理?
<script setup/>
本质是setup()
函数的语法糖,vue-loader在编译期间会把<script setup/>
内的代码编译成setup()
函数,把defineExpose()
内指定的变量编译为setup()函数的返回值。
(4)组件实例是什么?
每个单文件组件都有一个组件实例,组件实例是用来存放组件状态的,可以在setup()
函数内,通过getCurrentInstance()
获取组件实例。
(5)启动程序的时候,vue3单文件组件的初始化流程?
单文件组件由组件实例、相应vnode、相应node三部分组成。初始化的时候,先创建组件的实例;接着用父组件vnode初始化实例的props
、attrs
、slots
成员;然后执行setup()
函数;接着把模板编译成render()
函数;接着创建vnode;如果当前组件有子组件,则切换到子组件,重复上面的步骤;子组件都初始化完成后,把vnode同步到node上。
(6)createApp()
,mount()
做了什么工作?
程序在初始化的时候会依次调用createApp()
和mount()
函数,createApp()
负责执行平台相关的代码,mount()
负责执行平台无关的代码。createApp()
主要做了两件事,第一,创建一个渲染器,它可用于把vdom转化为平台相关的界面代码;第二,返回一个App
实例。mount()
的作用就是,创建组件实例、vdom、完成依赖收集、并通过渲染器把vdom渲染成平台相关的界面代码。
(7)组件的生命周期?
单个组件的生命周期:初始化阶段,先执行setup函数初始化实例,接着执行onBeforeMount
,最后执行onMounted
;运行阶段,先执行onBeforeUpdate
,再执行onUpdated
;关闭阶段,先执行onBeforeUnmount
,再执行onMounted
。
父子组件的生命周期:初始化阶段,先执行父组件的onBeforeMount
,然后执行子组件的onBeforeMount
,接着执行子组件的onMounted
,最后执行父组件的onMounted
;运行阶段、关闭阶段同理可推。
(8)什么是虚拟DOM?diff算法?
虚拟dom是用JavaScript语言描述的一个树形结构,用于缓存发生变化的响应式数据,当到达下次刷新周期的时候,一次性把所有发生变化的数据同步到实际dom上。diff算法是同步vdom和dom数据的算法,采用就地复用的策略实现高效同步。
(9)任务调度系统?
vue3内部维持了3类队列:pre队列——组件更新前置任务队列;queue队列——组件更新时的任务队列;post队列——组件更新后置任务队列。vue3内所有异步任务都会被推进这些队列,当本次宏任务执行结束的时候,会执行一个名为flushJob的微任务,该微任务会按照pre>queue>post的优先级,依次执行每个队列中的函数。
(10)异步刷新是什么?怎么实现的?
异步刷新指的是在响应式数据发生更改后,只更新vdom,不立即更新dom,当下一个刷新周期到来的时候,次性把所有发生变化的数据同步到实际dom上。
异步刷新是通过job队列+Promise+diff算法实现的,当响应式数据发生更改时候,会立即更新vdom上的数据,待下个刷新周期到来的前,开始执行flushJob微任务,flushJob会通过diff算法完成vdom和dom之间的数据同步。
(11)nextTick()
的实现原理?
nextTick()作用是,让回调函数在下一次刷新DOM的时候,具体来说在onUpdated后执行。vue3内的nextTick()是通过Promise实现,当微任务flushJob把Job队列中的所有函数都执行完了后,nextTick()回调会被推入微任务队列,然后开始执行。
(12)有哪三种Effect
?作用?
vue3组件内有3类Effect:computedEffect
、watchEffect
、renderEffect
。正是通过这些Effect才实现数据可监听函数的绑定,其中computedEffect
、watchEffect
在setup()
期间创建,而renderEffect
在onBeforeMount
和onMounted
之间创建。
(13)watch()
,watchEffect()
,computed()
的区别?
三者都用于实现数据和监听函数的绑定。原理上来说,三者的共同点是,都会在setup期间、即onBeforeMount之前,创建一个Effect对象,并执行依赖收集函数进行依赖收集。区别是,第一,watchEffect、computed的依赖收集函数和监听函数是同一个函数,而watch的依赖收集函数和回调函数是两个函数,这就导致了watchEffect和computed参数中的监听函数会在程序初始化期间执行一次,而watch参数中的监听函数默认不会执行;第二,computed参数中的监听函数在变量发生变化后立即执行,watchEffect、watch参数中的监听函数执行时机可由用户指定,可以立即执行,也可以异步执行。
(14)响应式编程的实现原理?
响应式编程的核心是通过发布订阅模式、Effect对象、重写get函数实现的。发布订阅模式实现数据和监听函数的绑定,Effect对象内封装有监听函数和调度策略,get函数则负责进行依赖收集和返回数据。
(15)ref()
和reactive()
的区别?
两者都用于实现响应式变量,区别在于reactive()
只可以封装引用类型,ref()
可以封装任意类型;reactive()
通过Proxy
实现,ref()
通过类封装,并重写get/set
实现。就目前而言,要访问ref()
封装的变量,需要通过value
函数获取。
(16)ref()
和ref(null)
的区别?
ref()
的作用是封装普通数据,实现响应式数据;ref(null)
的作用是获取子组件实例。
(17)attrs
和props
的区别?
attrs
和props
的作用都是,以模板属性的形式,从父组件传递数据给子组件。区别是,子组件中获取从父组件传过来的props
数据,首先需要用defineProps()
声明,而获取attrs
数据不需要声明。props
的优势是增加了代码可读性,attrs
的优势是传值更灵活、代码更简洁。
(18)provide()
和inject()
实现原理?
在祖先组件里通过provide()
提供的数据,可以在任意子孙组件里通过inject()
获取。它是通过原型链实现的。
(19)emit()
的实现原理?
emit()
是通过发布订阅模式实现的,子组件发送一个事件,父组件收到该事件会立即执行相应的函数。
(20)单向数据流的原因?
单向数据流指的是在 vue中约定,父组件传递给子组件的数据只能单向流动。也就是说,当一个父组件将其数据传递给一个子组件时,子组件不能直接修改该数据,而是需要通过向父组件发送事件来请求更改该数据。这种单向数据流的方式可以确保应用程序的数据流向清晰明确,易于理解和维护。
(21)模板的编译流程?在什么时候编译?
模板会先被解析成AST对象,然后通过遍历AST对象生成渲染函数。这个过程发生在setup()
函数执行结束后、onBeforeMount
之前。
(22)组件注册是什么?
若要在vue内使用自定义组件,需要先注册组件。组件注册可以看作是一种声明,用于告诉编译器,某个标签名是组件。组件注册分为局部注册和全局注册,全局注册的组件可以在整个程序内使用,局部注册的组件只可以在特定组件内使用。
(23)指令的实现原理?
vue3内的指令是,本质上是一系列钩子函数的集合,可以为这些钩子函数指定执行时机,包括:onBeforeMount
、onMounted
、onBeforeUpdate
、onUpdated
、onBeforeUnmount
和onUnmounted
。
(24)各种指令的区别和作用?
v-show
和v-if
的区别:v-show=false
的组件会被创建,但是不会显示;v-if=false
的组件不会被创建。
v-on
和v-bind
的区别:v-on
用于绑定事件和事件处理函数;v-bind
用于绑定变量。
v-text
,v-html
和插值表达式的区别:插值表达式存在闪屏的问题,而v-text
、v-html
不存在该问题;插值表达式和v-text
都会直接以字符串的形式显示html,v-html
则可以渲染html标签对。
(25)v-for
为什么要加key?v-for
为什么不能和v-if
一起使用?
v-for
要加key是因为,第一,vue的同步算法采用就地复用的策略,在没有key值的情况下,diff算法认为标签类型相同的元素可以进行复用;有key值的情况下,diff算法认为标签类型相同且key值相等的元素才可以复用。第二,vue只在v-model
实现了数据双向绑定,其它地方实现的都是单向绑定,<input/>
框内的文字发生变化,不会实时同步到数据上。
在vue3中,v-if
的优先级高于v-for
,导致v-if
访问不了v-for
中的变量,因此不能把v-if
和v-for
同时用在同一个元素上。
(26)v-model
的实现原理?
v-model
实现了数据的双向绑定,它是通过响应式数据+监听输入框事件实现的。
(27)hook是什么?
hook是一种编程封装思想,通过闭包实现,早在ES6诞生之前,这种编程封装思想在JavaScript中应用广泛,只不过在React17出现后,React官方把这种思想命名为hook,vue3借鉴react命名,也把这种封装思想称作hook。
(28)vue3插件是什么?
vue3内的所谓的插件不是常规意义上的插件,而是指mount()前执行的初始化函数,vue插件通常用来为 vue 添加全局功能。vue3内通过use()函数引入插件,use()的作用就是执行参数对象内的install()函数。
(29)什么是异步组件?
网页的渲染流程一般分两部,先从网络加载网页,然后渲染。异步组件指的是,首次加载和渲染网页的时候,不从网络上加载该组件对应的JavaScript文件,而是等到需要显示该组件的时候,再从网络上加载该组件并渲染显示出来。
(30)前端路由是什么?什么是路由懒加载?
路由能够根据不同的URL地址返回给用户不同的内容或页面,如果路由位于浏览器端,就是前端路由;如果路由位于服务器端,就是后端路由。
对于SPA单页应用,打包构建时,JavaScript包会变得非常大,会使得页面首次加载的时间变长 。若将不同路由对应的组件分割成不同的文件,然后当路由被访问的时候才从服务器加载对应文件,这就是路由的懒加载。