8.高频面试真题与标准回答
来源:https://my.feishu.cn/docx/ECCCdAzIeoB66ZxEvyTcNaqvnff
采集状态:已完成正文采集;已滚动到底部并复查
Vue 高频面试题精讲
💡
本文覆盖 Vue 3 面试中出现频率最高的问题,包含标准回答、代码示例与常见追问,适合结构化准备面试。
目录导航
1.
响应式原理
2.
Composition API
3.
生命周期
4.
组件通信
5.
性能优化
6.
对比分析React
一、响应式原理类
Q1:Vue 3 的响应式原理是什么?与 Vue 2 有什么区别?
简要回答
Vue 3 使用 Proxy 实现响应式,相比 Vue 2 的 Object.defineProperty,在性能、能力和可维护性上均有大幅提升。
拆解说明
Vue 3 的响应式核心机制:
1.
使用 Proxy 拦截对象的 get/set 操作
2.
track() 函数收集依赖(在 getter 中)
3.
trigger() 函数触发更新(在 setter 中)
4.
ReactiveEffect 类统一管理副作用函数
与 Vue 2 的区别:
特性
Vue 2
Vue 3
基础机制
Object.defineProperty
Proxy
支持新增/删除属性
否
是
支持数组索引
否(需重写原型)
是
嵌套代理
手动递归
懒加载递归代理
性能表现
初始化时递归全对象
按需代理,性能更优
常见追问
Q: 为什么不用 Object.defineProperty?
因为它有以下局限:
•
无法监听新增/删除属性
•
不支持数组索引变化
•
必须递归遍历所有嵌套属性
•
性能和维护成本高
Proxy 则可拦截对象的所有操作,是更现代的选择。
Q2:ref 和 reactive 有什么区别?
简要回答
ref 适合基本类型,reactive 用于对象和数组。ref 返回 .value 包裹的数据,reactive 返回 Proxy。
拆解说明
ref:
•
用于基本类型(string、number 等)
•
返回 RefImpl 对象,访问时需 .value
•
模板中自动解包
•
包装对象时会递归调用 reactive
reactive:
•
用于引用类型(对象、数组)
•
返回 Proxy,直接访问属性即可
•
是深度响应式,嵌套对象也响应
•
无法直接替换整个对象,否则会失去响应性
示例代码
import { ref, reactive } from 'vue'
const count = ref(0)
const user = reactive({ name: 'John', age: 25 })
const update = () => {
count.value++ // ref 需要 .value
user.name = 'Jane' // reactive 直接访问
}
常见追问
Q: 为什么有时候对象也用 ref?
以下场景推荐使用 ref 包装对象:
•
需要整体替换对象:user.value = newUser
•
和第三方库交互
•
需要接口统一
Q3:Vue 的响应式更新是同步还是异步的?
简要回答
Vue 的响应式更新是异步的,采用微任务队列进行 DOM 批量更新。
拆解说明
更新流程:
1.
修改响应式数据 → 触发 setter → 收集 effect
2.
effect 被放入更新队列
3.
DOM 更新合并执行
4.
下一事件循环中统一刷新 DOM
异步更新优势:
•
合并多次数据变动
•
减少渲染次数,提升性能
•
避免中间状态被渲染
示例代码
import { ref, nextTick } from 'vue'
const count = ref(0)
const update = async () => {
count.value = 1
count.value = 2
count.value = 3
console.log(document.querySelector('#count').textContent) // 仍是旧值
await nextTick()
console.log(document.querySelector('#count').textContent) // 此时更新完毕
}
常见追问
Q: nextTick 的原理是什么?
Vue 内部优先使用微任务调度(如 Promise.then),回退方案依次为:
•
MutationObserver
•
setImmediate
•
setTimeout
nextTick 本质是把回调放到 DOM 更新之后的微任务队列中执行。
二、Composition API 类
Q4:setup 函数的作用是什么?它与 data、methods 等选项有什么关系?
简要回答
setup 是 Composition API 的入口,用于整合和组织组件的逻辑。它在组件实例创建前执行,并取代了 Vue 2 中的 data、methods、computed 等选项。
拆解说明
setup 的核心职责:
1.
逻辑组织中心:在 setup 内部声明响应式状态、计算属性、侦听器和生命周期钩子。
2.
执行时机:在 props 被解析后、组件实例创建前执行。此时 this 还不是组件实例。
3.
参数:接收两个参数:props(响应式的,不能解构)和 context(包含 attrs, slots, emit)。
4.
返回值:返回一个对象,该对象中的属性和方法会暴露给模板和组件的其他部分。
与 Options API 的关系:
setup 旨在取代 Options API 中分散的逻辑。一个 setup 函数可以完成过去需要 data()、methods、computed、watch 和生命周期钩子多个选项才能完成的工作,让相关逻辑更内聚。
示例代码
import { ref, computed, onMounted } from 'vue'
export default {
props: {
initialValue: {
type: Number,
default: 0
}
},
setup(props, { emit }) {
// 1. 状态 (等同于 data)
const count = ref(props.initialValue)
// 2. 计算属性 (等同于 computed)
const doubleCount = computed(() => count.value * 2)
// 3. 方法 (等同于 methods)
const increment = () => {
count.value++
emit('change', count.value)
}
// 4. 生命周期钩子 (等同于 mounted)
onMounted(() => {
console.log('Component is mounted!')
})
// 暴露给模板
return {
count,
doubleCount,
increment
}
}
}
常见追问
Q: setup 里的 this 是什么?
在 setup 函数执行期间,this 是 undefined。这是为了避免与 Options API 的 this 指向组件实例的行为混淆。所有需要的功能都通过 props 和 context 参数提供。
Q5:watch 和 watchEffect 有什么区别?
简要回答
watch 需要明确指定侦听的数据源,更具体;watchEffect 会自动追踪其回调函数中使用的响应式依赖,更智能。
拆解说明
特性
watch
watchEffect
依赖追踪
手动指定,需明确传入要侦听的 ref 或 reactive 对象
自动追踪,无需指定,函数体内用到的依赖都会被追踪
首次执行
默认懒执行(lazy),仅在数据变化时执行。可配置 immediate: true
立即执行一次,然后当依赖变化时再次执行
回调参数
可以访问新值和旧值 ((newValue, oldValue) => ...)
无法访问旧值,只在新值变化时重新运行整个函数
使用场景
当你只想在特定数据变化时执行某个操作(如网络请求)
当一个副作用依赖多个数据源,或需要立即执行时
示例代码
import { ref, watch, watchEffect } from 'vue'
const userId = ref(1)
const userInfo = ref(null)
// --- watch 示例 ---
// 明确侦听 userId 的变化,并在变化时执行操作
watch(userId, async (newId, oldId) => {
console.log(`User ID changed from ${oldId} to ${newId}`)
// userInfo.value = await fetchUser(newId) // 根据 ID 获取新用户数据
})
// --- watchEffect 示例 ---
// 自动依赖 userId,会立即执行一次,之后每当 userId 变化都会再次执行
watchEffect(() => {
console.log(`Current User ID is: ${userId.value}`)
// 这个函数会立即运行,打印 "Current User ID is: 1"
})
const changeUser = () => {
userId.value++
}
常见追问
Q: 应该优先选择哪个?
•
当副作用与某个数据源强相关,且需要访问旧值时,优先用 watch。
•
当副作用依赖多个响应式数据,且逻辑简单直观时,watchEffect 更简洁。
•
需要停止侦听时,两者都会返回一个 stop 函数,调用它即可停止。
三、生命周期类
Q6:Vue 3 的生命周期钩子有哪些?与 Vue 2 相比有什么变化?
简要回答
Vue 3 的生命周期钩子在 Composition API 中使用,名称上基本是 Vue 2 钩子加上 on 前缀。最大的变化是 beforeCreate 和 created 被 setup 函数本身所取代。
拆解说明
Vue 3 (Composition API) vs Vue 2 (Options API) 钩子映射:
Vue 2 选项
Vue 3 (在 setup 中使用)
描述
beforeCreate
setup()
组件实例创建前,setup 是入口
created
setup()
组件实例创建后,逻辑在 setup 中完成
beforeMount
onBeforeMount
DOM 挂载前
mounted
onMounted
DOM 挂载后
beforeUpdate
onBeforeUpdate
DOM 更新前
updated
onUpdated
DOM 更新后
beforeDestroy
onBeforeUnmount
组件实例销毁前
destroyed
onUnmounted
组件实例销毁后
errorCaptured
onErrorCaptured
捕获子孙组件错误时调用
新增的调试钩子:
•
onRenderTracked: 跟踪虚拟 DOM 重新渲染时收集的依赖。
•
onRenderTriggered: 跟踪触发虚拟 DOM 重新渲染的事件。
示例代码
import { onMounted, onUpdated, onUnmounted } from 'vue'
export default {
setup() {
onMounted(() => {
console.log('组件已挂载')
})
onUpdated(() => {
console.log('组件已更新')
})
onUnmounted(() => {
console.log('组件已卸载')
// 在这里清理副作用,如定时器、事件监听等
})
}
}
常见追问
Q: 为什么不再需要 beforeCreate 和 created?
因为 setup 函数的执行时机覆盖了这两个钩子的功能。setup 在 props 解析之后、组件实例创建之前执行,所有需要在 created 中完成的数据初始化和响应式设置,都可以在 setup 中直接完成,使其更为直观和统一。
四、组件通信类
Q7:Vue 3 中有哪些主要的组件通信方式?
简要回答
Vue 3 的核心通信方式包括:props / emit、provide / inject、v-model、以及通过 ref 和 defineExpose 实现的父子组件直接访问。对于复杂场景,则推荐使用 Pinia 等全局状态管理库。
拆解说明
1. props / emit(父 -> 子,子 -> 父)
•
props: 父组件向子组件传递数据,是单向数据流。
•
emit: 子组件通过触发事件向父组件发送消息。Vue 3 推荐在组件中使用 emits 选项来显式声明它会触发哪些事件,这有助于代码的可读性和维护性。
2. provide / inject(祖先 -> 后代)
•
用于跨越多层级的组件通信,避免“属性透传”(Prop Drilling)。
•
祖先组件通过 provide() 提供数据或方法,任何后代组件都可以通过 inject() 来注入并使用它们。
•
注意:provide 的数据默认不是响应式的,除非你提供的是一个 ref 或 reactive 对象。
3. v-model(双向绑定语法糖)
•
在 Vue 3 中,组件上的 v-model 被简化了。它等价于传递一个 modelValue prop 并监听一个 update:modelValue 事件。
•
Vue 3 还支持在单个组件上使用多个 v-model,例如 v-model:title 和 v-model:content。
4. ref / defineExpose(父 -> 子)
•
父组件可以通过模板 ref 获取子组件的实例。
•
默认情况下,子组件的 setup 是私有的。子组件必须使用 defineExpose 宏来明确暴露希望父组件访问的属性或方法。
5. 全局状态管理(Pinia / Vuex)
•
对于非父子关系、跨多个组件共享的状态,使用 Pinia(Vue 官方推荐)或 Vuex 是最佳实践。
•
它们提供了一个集中的、可预测的状态存储,并与 Vue Devtools 深度集成。
示例代码
Received from child: {{ childResponse }} User name from child: {{ userName }} {{ message }}
{{ item.name }}