Vue
Vue 框架相关面试问题
Vue2
1. 简述 MVVM
定义:Model-View-ViewModel === 模型-视图-视图模型
- Model: 构成页面内容的相关数据
- View:展示数据的页面
- ViewModel:它是连接 view 和 model 的桥梁,数据会绑定到 viewModel 层并自动将数据渲染到页面中
2. 生命周期
每个 Vue 实例在创建时都会经过一系列的初始化过程,vue 的生命周期钩子,就是说在达到某一阶段或条件时去触发的函数,目的就是为了完成一些动作或者事件
-
创建阶段:vue 实例被创建
-
beforeCreate: 创建前,此时 data 和 methods 中的数据都还没有初始化 ,不可以使用 data 中的数据
-
created: 创建完毕,data 中有值,可以发起服务端请求,获取数据
-
挂载阶段: vue 实例被挂载到真实DOM节点
-
beforeMount:组件挂载之前,可以发起服务端请求,获取数据
-
mounted: 组件挂载完成,此时可以操作 Dom
-
更新阶段:当 vue 实例里面的 data 数据变化时,触发组件的重新渲染
-
beforeUpdate:数据修改后,页面更新之前
-
updated:页面更新完成,可以获取最新的 dom
-
销毁阶段:vue 实例被销毁
-
beforeDestroy:实例被销毁前,此时可以手动销毁一些异步操作(定时器,事件监听,请求)
-
destroyed:实例销毁后
3. 父子组件生命周期执行顺序
-
组件创建过程:
-
父组件 beforeCreate
-
父组件 created
-
父组件 beforeMount
-
子组件 beforeCreate
-
子组件 created
-
子组件 beforeMount
-
子组件 mounted
-
父组件 mounted
-
更新过程:
-
父组件 beforeUpdate
-
子组件 beforeUpdate
-
子组件 updated
-
父组件 updated
-
销毁过程:
-
父组件 beforeDestroy
-
子组件 beforeDestroy
-
子组件 destroyed
-
父组件 destroyed
4. computed 与 watch
-
watch
-
主要用来监听特定数据的变化
-
key 是需要监听的属性,value 是变量改变后的回调函数
-
可以在数据变化时执行异步或开销较大的操作时使用
-
computed
-
必须 return 计算后的结果
-
支持缓存,只有依赖的数据发生了变化,才会重新计算
-
如果一个变量是由其他响应式变量计算生成的,一般会使用 computed
5. 组件通讯
- 父 -> 子: props
- 子 -> 父:自定义事件,子组件通过 $emit 调用父组件的函数,通过函数的参数传给父组件
- 兄弟组件或者无关系组件:Event Bus、vuex
- 跨级组件通讯:Provide、inject
特殊情况(没人问就别提):
- 父组件中调用子组件方法和数据:通过 ref 获取子组件实例对象,调用子组件方法
- 子组件直接调用父组件方法和数据:通过 this.$parent 获取父组件实例对象,调用父组件方法和数据
6. v-for 中 key 的作用
- 元素的唯一标识符,使用key可以帮助Vue更快地定位到需要更新的节点,减少不必要的DOM操作。
- 数据变化时,Vue.js使用key来识别哪个元素被更改、添加或删除。如果列表中的元素顺序发生变化,Vue会通过key来决定如何高效地更新DOM。
7. slot 是什么?
- vue 中的插槽可以在组件的模板中预留一个或多个位置,调用组件时组件双标签里面的内容就会自动替换组件模板中 slot 位置
- 使用场景:当组件中某些位置的内容只有调用时才能确定展示什么内容的需求可以使用插槽来实现,例如弹窗组件展示的内容可以使用插槽实现
// App.vue
<Child>
<p>这是要替换slot的内容</p>
</Child>
// Child.vue
<template>
<div>
<slot></slot>
</div>
</template>
8. v-if 和 v-show 的区别
- v-if 是添加或者删除 DOM 元素
- v-show 是修改元素样式,添加或删除 display:none
9. 组件上如何使用 v-model
- 组件上的 v-model 就是一个语法糖
- 相当于给子组件传入一个 value 属性和 input 自定义事件
- 在子组件中通过 value 接受 v-model 传入的变量,通过 $emit('input') 修改 v-model 的值
// Child.vue
<template>
<div>
<input type="text" :value="value" @input="$emit('input', $event.target.value)">
</div>
</template>
<script>
export default {
props: ['value']
}
</script>
// App.vue
<Child v-model="text"/>
/*
// 组件上使用 v-model 等同于此写法
<Child
v-bind:value="searchText"
v-on:input="searchText = $event"
/>
*/
10. v-model 指令的原理
- 标签上的 v-model 是一个语法糖,相当于给 value 属性赋值变量,通过 input 事件修改变量
<input :value="变量" @input="变量 = $event.target.value" />
11. data 为什么是一个函数
- 如果data直接作为一个对象提供,那么所有组件实例将共享同一个对象,修改数据时组件会互相影响
- 组件复用时通过返回一个新对象,每个组件实例都可以拥有自己的数据副本互不影响
12. keep-alive 的作用
- keep-alive 会缓存组件实例,组件隐藏时不会直接销毁
- 被 keep-alive 缓存的组件有两个生命周期, activated 组件显示时执行,deactivated 组件隐藏时执行
- keep-alive 提供了 include、exclude、max 属性:
- include:要缓存的组件
- exclude:要排除的组件
- max:最多可以缓存多少组件实例
13. $nextTick 的作用和原理
作用:nextTick 接受一个回调函数作为参数,等待 dom 元素更新之后执行此函数,可以在此函数中获取到最新的dom元素。
原理:
- Vue 的响应式系统是异步的,当数据变化时,Vue 会将更新操作放入一个队列中,并在下一个 DOM 更新周期中执行这些操作。
- nextTick 将传入的回调函数包装成异步任务,异步任务又分微任务和宏任务,为了尽快执行所以优先选择微任务实现。
- 首先判断当前浏览器是否支持 Promise 和 MutationObserver,如果不支持就判断是否支持 setImmediate,最后降级使用 setTimeout。
注意:nextTick 是宏任务还是微任务取决于当前浏览器支持哪种语法,如果支持 Promise 或者 MutationObserver 就是微任务,setImmediate 或者 setTimeout 是宏任务。
14. 为什么避免 v-if 和 v-for 在同一标签使用
- 当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中,可能会导致性能问题。
- 如果想要通过 v-if 实现筛选时可以改成计算属性。
- 如果想跳过循环的执行可以将 v-if 放到 v-for 的父级元素。
15. vue 响应式的原理
- Vue 的响应式数据是通过数据劫持配合观察者模式实现的。
- 数据劫持是通过 Object.defineProperty() 来劫持对象属性的 setter 和 getter 操作。
- 在数据变动时通过观察者模式通知页面更新。
注意:Object.defineProperty 无法监听对象中新增的属性和数组的下标修改,可以通过 $set 给对象新增响应式属性,通过数组的方法修改数组内容,vue 给数组重写了方法,可以在调用方法修改数组时更新页面。
16. $set
作用:给对象新增响应式属性,通过数组下标修改数据
语法:this.$set(obj, 'key', 'value')
17. vue-router 两种模式的区别和原理
-
hash —— 地址栏 URL 中有 #
-
原理:通过 hashchange 事件监听地址栏 # 后的内容变化展示对应的组件
-
history —— 利用了 HTML5 History 中的 pushState() 和 replaceState() 方法实现路由
-
history 模式需要后台配置支持,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,直接返回 index.html 文件,防止页面刷新显示 404。
18. vue 路由传参
-
query 传参,把参数添加到 url ?之后
-
跳转
this.$router.push({ path: '路由地址', query: { id: 100 } }) -
获取:
this.$route.query.id -
动态路由传参,必须先在路由表中配置
// 必须在路由表中先配置
const routes = [
{
path: '/detail/:id',
name: 'detail',
component: Detail
}
]
// 跳转
this.$router.push({
name: 'detail',
params: {
id: 100
}
})
// 获取
this.$route.params.id
19. vue 导航守卫
- 全局导航守卫:所有路由都会触发全局导航守卫
- 全局前置守卫:beforeEach,每次路由切换之前调用,可以做登录拦截
- 全局后置守卫:afterEach,每次路由跳转完成之后调用
- 路由独享守卫:beforeEnter
- 组件内守卫:
- beforeRouteEnter:进入该组件前执行,不能使用 this
- beforeRouteUpdate:在当前路由改变,但是该组件被复用时调用
- beforeRouterLeave:离开该组件时执行
注意:导航守卫的三个参数:
to:跳转后的路由对象
from:跳转前的路由对象
next:是否继续执行跳转,如果不调用 next 就不会继续跳转
20. $route 和 $router 的区别
- $route 是当前路由信息对象,包含当前路由的基本信息
- $router 是 VueRouter 实例对象,包含跳转路由的方法等
21. 状态管理 vuex 的使用流程
- Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
- store 内有 5 个属性,分别是 state、getter、mutation、action、module
- state 存放数据,组件内通过 this.$store.state 或者 mapState 辅助函数获取
- mutations 是修改 state 数据的唯一方式,必须是同步函数,在组件内通过 this.$store.commit(函数名, 参数) 调用,或者使用 mapMutations 辅助函数映射到组件的 methods 内
- actions 处理 store 中的异步操作,然后触发 mutations 中的函数修改 state,在组件中通过 this.$store.dispatch(函数名, 参数) 触发 actions 中的函数,或者通过辅助函数 mapActions 映射到组件的 methods 中
- getters 可以对 state 的数据进行计算,类似组件中的 computed
- module:当应用比较复杂时使用 module 模块化拆分 store ,每个模块有自己的 state、mutations、actions
22. 如何让 css 只在当前组件生效
- 给组件内的 style 标签添加 scoped 属性
<style scoped>
原理:给组件内的标签添加 data-v- 开头的自定义属性,CSS 选择器也添加这些属性通过,属性选择器添加样式, 例如:.example[data-v-123456] { font-size: 18px; }
23. 样式穿透
作用:使用 ::v-deep 样式穿透修改子组件和 UI 组件库的内组件的样式
scoped 虽然避免了组件间样式污染,但是很多时候我们需要修改组件中的某个样式,但是又不想去除 scoped 属性
<style scoped>
.parent ::v-deep .child {
color: red;
}
</style>
Vue3
1. vue2 和 vue3 的主要区别
- Vue 2 主要使用 Options API,Vue 3 主要使用组合式 API 可以更加灵活地组织逻辑代码。
- 响应式原理的的变化:
- Vue 2 的响应式系统基于
Object.defineProperty,有一些局限性,比如无法检测对象属性的添加或删除。 - Vue 3 使用了 Proxy 对象来实现响应式系统。Proxy 可以监听对象的任何操作,包括属性的添加、删除等
- vue2抽取公用逻辑使用混入,vue3 使用 hooks
- vue 2 中
v-for优先级高于v-if, Vue 3 中v-if优先级高于v-for
2. vue2 和 vue3 响应式原理的变化
2.1. Vue2 的响应式原理
Vue2 使用的是Object.defineProperty()来实现数据劫持,即在数据对象的每个属性上定义 getter 和 setter。在访问或修改某个属性时,getter 和 setter 会被触发,从而实现响应式。
-
缺点:
-
只能劫持对象的已有属性,无法监控对象新增或删除的属性。
-
对象深层次的嵌套结构需要递归地为每个属性都定义 getter 和 setter,性能损耗较大。
2.2. Vue3 的响应式原理
Vue3 的响应式系统基于 Proxy 实现。Proxy 可以代理整个对象,而不仅仅是对象的每个属性。
-
优点:
-
能够劫持整个对象,不再局限于对象已有的属性,新增和删除的属性也能被自动监控到。
-
对数组、对象、甚至 Map、Set 等复杂数据结构的变动也能被监控。
-
无需递归遍历对象的每个属性,减少了性能开销。
3. vue3中的组件通信方式
- 父传子:通过props来进行传参 父组件通过数据绑定给子组件传递数据,子组件通过 defineProps()方法来接收
- 子传父:通过$emit 来进行传参 子组件通过emit 来调用父组件的方法以传参的形式来进行数据传递
- provide /inject:它可以给所有后代组件传递数据通过proived来传递数据通过inject来接收
- 使用 pinia:使用vue3中的状态管理工具来进行组件的通信
4. vue3中的生命周期
创建阶段:setup
挂载阶段: onBeforeMount onMounted
更新阶段: onBeforeUpdate onUpdated
销毁阶段: onBeforeUnmount onUnmounted
keep-alive 标签:主要用于保留组件状态或避免重新渲染。
渲染:onactivated 隐藏:onDeactivated
5. ref 和 reactive 的区别
- ref
- 适用于任意类型
- 访问值需要
.value - 可以重新整体赋值
- reactive
- 只适用对象 / 数组
- 直接访问属性,无需 .value
- 不能整体替换对象
6. computed / watch / watchEffect 区别
- computed:用于从现有数据派生出新值,有缓存,依赖不变不重新计算,必须有返回值,不能有副作用。
- watch:显式指定侦听源,懒执行(默认不会在初始化时运行),能获取到变化前后的新旧值,适合执行异步操作或需要对比变化的场景。
- watchEffect:自动追踪函数内部用到的所有响应式数据,初始化时立即执行一次,无法获取旧值,适合依赖较多且不需要旧值的副作用。