前言
面试题整理自 Vue面试专题,题解结合了个人的思考和理解,供大家参考。
个人觉得村长的题目选的都挺好的,而且题解也很全面,就是题解比较书面,大家实际面试的时候可以尽量口语化,按照答题思路组织语言,把答案附上自己的风格说出来,关于面试技巧有些话复述不出来还可以通过举例的方式表达等等等。。。
题目的顺序是按照观众点赞评论收藏等数据计算出的评分按大到小排名的,排在前面的题目相对更重要观众更感兴趣
花了挺大劲整理的,面试前可以看看 就酱~ (我恨八股文)
1. 简述 Vue 生命周期
答题思路:
Vue
生命周期是什么?Vue
生命周期有哪些阶段?Vue
生命周期的流程?- 结合实践
- 扩展:在
Vue3
中生命周期的变化
回答范例:
- 生命周期这个词应该是很好理解的,在我们生活中就会常常碰到,比如谈到一个人的生命周期,我们会说人这一生会经历婴儿、儿童、少年、青年、中年、老年这几个阶段。 而
Vue
的生命周期也是如此,在Vue
中的每个组件都会经历从创建到挂载到更新再到销毁这几个阶段,而在这些阶段中,Vue
会运行一种叫做生命周期钩子的函数,方便我们在特定的阶段有机会添加上我们自己的代码。 - Vue 生命周期总共可以分为
8
各阶段:创建前后、挂载前后、更新前后、销毁前后,以及一些特殊场景的生命周期(keep-alive
激活时、捕获后代组件错误时)。Vue3
中还新增了三个用于调试和服务端渲染场景。 - 这几个阶段对应的钩子函数 API依次为:
beforeCreate
create
beforeMount
mounted
beforeUpdate
updated
activated(keep-alive 激活时调用)
deactivated(keep-alive 停用时调用)
beforeDestory
destoryed
errorCaptured(捕获子孙组件错误时调用)
。
在 Vue3 中的变化 绝大多数只要加上前缀 on 即可,比如mounted
变为onMounted
,除了beforeDestroy
和destroyed
被重新命名为beforeUnmount
和unMounted
(这样与前面的beforeMount
和mounted
对应,强迫症表示很赞🤣) beforeCreate
在组件创建前调用,通常用于插件开发中执行一些初始化任务;created
组件创建完毕调用,可以访问各种数据,请求接口数据等;mounted
组件挂载时调用 可以访问数据、dom
元素、子组件等;beforeUpdate
更新前调用 此时view
层还未更新,可用于获取更新前的各种状态;updated
完成更新时调用 此时view层已经完成更新,所有状态已经是最新的了;beforeUnmount
实例被销毁前调用,可用于一些定时器或订阅的取消;unMounted
销毁一个实例时调用 可以清理与其他实例的链接,解绑它的全部指令以及事件监听器。- 在 Vue3 中:
setup
是比created
先执行的; 而且没有beforeCreate
和created
。
2. Vue 中如何做权限管理
- 权限管理一般需求就是对页面权限和按钮权限的管理
- 具体实现的时候分前端实现和后端实现两种方案: 前端方案会把所有路由信息在前端配置,通过路由守卫要求用户登录,用户登录后根据角色过滤出路由表,然后在动态添加路由。比如我会配置一个
asyncRoutes
数组,需要认证的页面在路由的meta
中添加一个roles
字段,等获取用户角色之后取两者的交集,若结果不为空则说明可以访问。过滤结束后剩下的路由就是用户能访问的页面,最后通过router.addRoutes(accessRoutes)
方式动态添加路由即可。
后端方案会把所有页面路由信息存在数据库中,用户登录的时候根据其角色查询得到其能访问的所有路由信息返回给前端,前端再通过addRoute
动态添加路由信息。
按钮权限的控制通常会实现一个指令,例如v-permission
,将按钮要求角色通过值传给v-permission
指令,在指令的mounted
钩子中可以判断当前用户角色和按钮是否存在交集,有就保留按钮,没有就移除按钮。 - 纯前端方案的优点是实现简单,不需要额外权限管理页面,但是维护起来问题比较大,有新的页面和角色需求就要修改前端代码和重新打包部署;服务端方案就不存在这个问题,通过专门的角色和权限管理页面,配置页面和按钮权限信息到数据库,应用每次登陆时获取的都是最新的路由信息。
自己的话: 权限管理一般分页面权限和按钮权限,而具体实现方案又分前端实现和后端实现,前端实现就是会在前端维护一份动态的路由数组,通过用户登录后的角色来筛选它所拥有权限的页面,最后通过 addRoute
将动态添加到 router
中;而后端实现的不同点就是这些路由是后端返回给前端,前端再动态添加进去的。 按钮权限一般会实现一个 v-permission
,通过判断用户有没有权限来控制按钮是否显示。 纯前端方案的优点是实现简单,但是维护问题大,有新的页面和角色需求都需要改代码重新打包部署,服务端则不存在这个问题。
3. Vue 中双向绑定的使用和原理
回答思路:
- 什么是双向绑定?
- 双向绑定的好处?
- 在什么地方使用双向绑定?
- 双向绑定的使用方式、使用细节、Vue3中的变化
- 原理实现描述
回答:
- Vue中的双向绑定是一个指令
v-model
,它可以绑定一个响应式数据到视图,同时视图中变化也能改变该值。 v-model
是一个语法糖,它的原理(默认请情况下)就是通过:value
将变量挂到dom
上,再通过input
事件 监听dom
的变化改变变量的值。使用v-model
的好处就是方便呀,减少了大量的繁琐的事件处理,提高开发效率。- 通常在表单上使用
v-model
,还可以在自定义组件上使用,表示某个值得输入和输出控制。 - 可以结合修饰符做进一步限定(lazy/number/trim),用在自定义组件上时有些不同,它相当于是给了子组件一个
modelValue
的 属性 和update:modelValue
的 事件; 在 Vue3 中还可以用参数形式指定多个不同的绑定,如v-model:foo
这个时候就相当于 给了子组件一个foo
的 属性 和update:foo
的事件。 v-model
作为一个指令,它的原理就是 Vue 编译器会把它转换成 value属性绑定和input的监听事件,上面说过是默认情况下,实际上编译器会根据表单元素的不同分配不同的事件,比如checkbox
和radio
类型的input
会转换为checked
和change
事件。
4. Vue 组件之间通信有哪些?
Vue 组件之间通信有以下这么几种:
props
$emit
、$on
、$off
、$once
(后三者在Vue3中已被废除)$children
(Vue3中废除)、$parent
$attrs
、$listeners
(Vue3中废除)ref
$root
eventbus
(Vue3中不好使了,需要自己封装)vuex
、pinia
provide + inject
以上的方法长按使用场景可以分为:
- 父子组件之间可以使用
props
/$emit
/$parent
/ref
/$attrs
- 兄弟组件之间可以使用
$parent
/$root
/eventbus
/vuex
- 跨层及组件之间可以使用
eventbus
/vuex pinia
/provide + inject
5.你了解哪些 Vue 性能优化方法?
- 路由懒加载:有效拆分
App
尺寸,访问时才异步加载
const router = createRouter({ routes: [ { path : '/foo', component: () => import('./foo.vue)} ] }) 复制代码
keep-alive
缓存页面:避免重复创建组件实例,且能保存缓存组件状态
<keep-alive> <router-view v-if="$route.meta.keepAlive == true"></router-view> </keep-alive> <router-view v-if="$route.meta.keepAlive != true"></router-view> 复制代码
- 使用
v-show
复用DOM
:避免重复创建组件 v-for
遍历避免同时使用v-if
(实际上这在 Vue3 中是错误的写法)v-once
和v-memo
: 不再变化的数据使用v-once
;按条件跳过更新时使用v-memo
- 长列表性能优化:如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容。一些开源库(
vue-virtual-scroller
/vue-virtual-scroll-grid
) - 事件的销毁:Vue组件销毁时,会自动解绑它的全部指令以及事件监听器,但是仅限于组件本身的事件。
- 图片懒加载,自定义
v-lazy
指令 (参考项目:vue-lazyload
) - 第三方插件按需引入
element-plus
避免体积太大 - 子组件分割策略:较重的状态组件适合拆分
SSR
服务端渲染 解决首屏渲染慢的问题
6. 刷新后 Vuex 状态丢失怎么解决?
思路:
- 刷新后 Vuex 状态为什么会丢失?
- 解决方法
- 第三方库以及原理探讨
- 个人理解
回答:
- 因为 Vuex 只是在内存中保存状态,刷新后就会丢失,如果要持久化就要存起来。
- 可以是用
localStorage
存储Vuex
的状态,store
中把值取出来作为state
的初始值,提交mutation
的时候就存入localStorage
。 - 可以用
vuex-persist
、vuex-persistedstate
这种插件,可以通过插件选项控制哪些需要持久化。内部的原理就是通过订阅mutation
变化做统一处理。 - 这里有两个问题,一是如果用户手动改了
localStorage
怎么办?那我Vuex
里的状态不是也改变了?二是由于localStorage API
的原因只能存储字符串,所以我们只能将数据通过JSON.stringify
转换为字符串,而当我们存储的数据为Map
、Set
、Function
这种引用类型的数据时,JSON.stringify
转换后会变味{}
而丢失。
对应第一个问题我的解决方法是可以通过 监听 storage
事件来清除数据
window.addEventListener("storage", function () { localStorage.clear(); window.location.href = '/login' console.error("不要修改localStorage的值~~~"); }); 复制代码
对于第二个问题没办法了,只能选择不适用 Map
和 Set
这种引用类型。
7. Vue3 为什么用 Proxy 替代 defineProperty ?
思路:
- 属性拦截的几种方式
- defineProperty的问题
- Proxy的优点
- 其他考量
回答:
JS
中做属性拦截常见的方式有三种:defineProperty
、getter/setters
和Proxy
Vue2
中使用defineProperty
的原因是, 2013 年只能使用这种方式,由于该API
存在一些局限性,比如对于数组的拦截有问题,为此Vue
需要专门为数组响应式做一套实现。另外不能拦截那些新增、删除属性;最后defineProperty
方案在初始化时需要深度递归遍历处理对象才能对它进行完全拦截,明显增加了初始化的时间。- 以上两点在
Proxy
出现后迎刃而解,不仅可以对数组实现拦截,还能对Map
、Set
实现拦截;另外Proxy
的拦截也是懒处理行为,如果用户没有访问嵌套对象,那么也不会实施拦截,这就让初始化的速度和内存占用改善了。 Proxy
有兼容性问题,完全不支持IE
8. 怎么实现路由懒加载?
思路:
- 必要性
- 何时用
- 怎么用
- 使用细节
回答:
- 当打包构建时,Javascript 抱回变得非常大,影响页面加载。利用路由懒加载我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应最贱,这样更加高效,是一种优化手段。
- 一般来说,对于所有的路由都使用动态导入是个好主意
- 给
component
选项配置一个返回 Promise组件的函数就可以定义懒加载路由.例如:
{ path: '/login', component: () => import('../views/login/Login.vue') }, 复制代码
- 结合注释
{ path: '/login', component: () => import(/* webpackChunkName: "login" */'../views/login/Login.vue') }, 复制代码
vite中结合rollupOptions定义分块 5. 路由中不能使用异步组件
9. history模式 和 hash 模式有何区别?
- Vue-Router 有三个模式,其中 history 和 hash 更为常用。两者差别主要在显示形式和部署上,
- hash模式在地址栏现实的时候有一个
#
,这种方式使用和部署都较简单;history模式url看起来更优雅没关,但是应用在部署时需要做特殊配置,web服务器需要做回退处理,否则会出现刷新页面404的问题。 - 在实现上
hash
模式是监听hashchange
事件触发路由跳转,history
模式是监听popstate
事件触发路由跳转。
10. 说说 nextTick 的使用和原理?
- 在
Vue
中nextTick
是等待下一次DOM
更新刷新的工具方法。 Vue
有一个异步更新策略,意思是如果数据变化,Vue
不会立刻更新DOM
,而是开启一个队列,把组件更新函数保存在队列中,在同一时间循环中发生的所有数据变更会异步的批量更新。这一策略导致我们对数据的修改不会立刻体现在DOM
上,此时如果想要获取更新后的DOM
状态,就需要使用nextTick
nextTick
接受一个函数,我们可以在这个函数内部访问最新的DOM
状态 在开发时,有两个场景我们会用到nextTick
:
created
中想要获取DOM
;- 响应式数据变化后获取
DOM
更新后的状态;
nextTick
的原理:在Vue
内部,nextTick
之所以能够让我们看到DOM
更新后的结果,是因为我们传入的callback
会被添加到队列刷新函数的后面,这样等队列内部的更新函数都执行完毕,所有DOM
操作也就结束了,callback
自然能够获取最新的DOM
值。
11. v-for 和 v-if 优先级
先回答答案: 在 vue2
中, v-for
的优先级更高 但是在 vue3
中, v-if
的优先级更高
拓展: 无论什么时候,我们都不应该把 v-for
和 v-if
放在一起, 怎么解决呢?一是可以定义一个计算属性,让 v-for
遍历计算属性。二是可以把 if
移到内部容器里(ul
ol
)或者把v-for
移植外部容器(template
)中
12. 如何监听 Vuex 状态变化?
- watch
- store.subscribe()
watch
方式,可以以字符串形式监听 $store.state.xx
; subscribe
方法参数是一个回调函数,回调函数接受mutation
对象和 state
对象,可以通过 mutation.type
判断监听的目标。 wtach 方法更简单好用, subscribe
会略繁琐,一般用 vuex
插件中(可以提一下vuex的持久化插件vuex-persist
、vuex-persistedstate
)
13. 你觉得 Vuex 有什么缺点?
- 不支持持久化,页面刷新状态就会丢失
- 使用模块比较繁琐
- 不支持
ts
(或者说很不友好)
vue3 + pinia 会是更好的组合。
14. ref 和 reactive 异同点?
- 两者都能返回响应式对象,
ref
返回的是一个响应式Ref
对象,reactive
返回的是响应式代理对象。 ref
通常是处理单值得响应式,reactive
用于处理对象类型的数据响应式ref
需要通过.value
访问, 在视图中会自动脱ref
,不需要.value
,ref
可以接收对象或数组但内部依然是reactive
实现的;reactive
如果接收Ref
对象会自动脱ref
;使用展开运算符展开reactive
返回的响应式对象会使其失去响应性,可以结合toRefs()
将值转换为Ref
对象后再展开。reactive
内部使用Prxoy
代理拦截对象各种操作,而ref
内部封装一个RefImpl
类,设置get value/set value
,拦截用户对值得访问。
16. Vue 中如何扩展一个组件?
- 逻辑扩展:
mixins
、extends
、composition api
: - 内容扩展:slots
mixins
很灵活,但是会冲突很混乱。extends
是一个不太常用的选项,更 mixins
的不同是它只能扩展单个对象,优先级比 mixins
高。
混入的数据和方法 不能明确判断来源 而且可能和当前组件内变量 产生命名冲突,composition api 可以很好解决这些问题,利用独立出来的响应式模块可以很方便的编写独立逻辑并提供响应式数据局,增强代码的可读性和维护性。
扩展:Vue.mixin(全局混入) Vue.extend(有点像是 类/组件的继承 创建一个子类)
17. vue-loader 是什么?
vue-loader
是用于处理单文件组件(SFC)的webpack loader- 因为有了
vue-loader
,我们才能用.vue
文件形式编写代码,将代码分割为template
script
style
webpack
在打包的时候,会以loader
的方式调用vue-loader
vue-loader
被执行时,它会对SFC
中的每个语言块用单独的loader
链处理,最后将这些单独的块装配成最终的组件模块
18. 子组件能否修改父组件数据
不能直接改。
组件化开发中有一个单向数据流原则,不在子组件修改父组件数据是个常识
如果你确实需要改,请通过emit向父组件发送一个事件,在父组件中修改
19. 怎么定义动态路由,怎么获取传过来的动态参数?
我么可以在路径中使用一个动态字段来实现,例如/users/:id
其中 :id
就是路径参数。 可以通过 this.$route.parmas
获取,参数还可以有多个, $route
对象还公开了其他有用的信息如 query
hash
等。
Vue 面试题汇总(二):https://developer.aliyun.com/article/1414950