Vue3.0面试题汇总
Composition API 是 Vue 3 的重要特性。Vue 2 项目中,大型组件的逻辑容易分散在 data、computed、methods、watch 中,mixin 等复用方式也有命名冲突和来源不清晰的问题,TypeScript 推断能力有限。Composition API 提供了按逻辑关注点组织、提取和复用代码的另一种方式,但不是万能方案,Options API 在 Vue 3 中仍然受支持。
以下 14 题保留原文结构,并按当前官方文档修正。
一、Options API 与 Composition API 的区别?
- Options API 通过
data、computed、methods、watch等选项组织组件。它对小型和中型组件很直观;组件复杂后,同一功能可能分散在多个选项块。 - Composition API 把同一逻辑关注点的状态、计算属性、方法和副作用放在一起,可提取为 composable,使复杂功能更内聚。
- 两个主要收益:
- 逻辑组织:修改一个功能时减少在多个选项块之间跳转。
- 逻辑复用:通过函数参数和返回值明确数据来源,降低 mixin 的命名冲突和隐式合并问题。
Composition API 由普通函数和变量组成,通常有更自然的 TypeScript 推断,也减少了 this 指向问题。但它并不自动保证代码更清晰,也不自动消除命名或隐式依赖;设计不当的 composable 同样难维护。tree-shaking 依赖 ESM 与构建工具,而不是“所有 Composition API 函数都必然被摇掉”。
二、Vue 3 性能提升主要体现在哪些方面?
1. 编译阶段优化
- patch flags 与 block tree:编译器标记已知动态节点和动态属性,运行时对这些位置走快速更新路径,而不是无差别遍历整棵子树。
- 静态提升:可安全复用的静态 VNode 或静态属性会被提升或缓存,避免每次渲染重复创建。编译产物中的负数标记属于内部优化细节,不应当作“所有静态节点永远不 diff”的公共 API 保证。
- handler caching:编译器在安全时缓存内联事件处理器,减少重复创建函数和不必要的监听器更新;它不是用户手工打开的运行时开关。
- 静态字符串化与 SSR:客户端编译可把连续静态内容压缩成 static vnode 并以 HTML 挂载;SSR 通常编译为 HTML 字符串输出。两者目标相近,但产物和执行环境不同。
2. 源码与构建体积
Vue 3 提供 ESM 具名 API,支持 tree-shaking 的 bundler 可以移除未引用的可裁剪代码。最终体积取决于 Vue 版本、入口、依赖、生产配置和实际使用功能,不能保证固定“缩小一半”。
3. 响应式系统
Vue 2 递归转换初始化时已有属性,并包装七个数组变异方法;它不能自动检测对象属性直接新增/删除、数组索引和 length 的直接修改。Vue 3 的 reactive() 以 Proxy 为基础,可以拦截这些操作。原生 Proxy 只代理一层,Vue 通过访问嵌套对象时继续转换来形成深层响应式。
4. 实际优化原则
版本特性不是固定性能承诺。应先用 Vue DevTools、浏览器 Performance 面板测量,再按瓶颈采用稳定 props、代码分割、虚拟列表、v-once、v-memo 等手段。
三、Vue 3 为什么用 Proxy 替代 defineProperty?
Vue 2 的 Object.defineProperty() 需要逐个转换已有属性。它能递归观察已有嵌套对象,也能通过包装 push、pop、shift、unshift、splice、sort、reverse 响应数组变异;“数组 API 无法监听”是错误说法。它的边界主要是新增/删除对象键、索引赋值与修改 length。
Proxy 面向目标对象的操作,可拦截 get、set、deleteProperty、has、ownKeys 等,因此 Vue 3 能覆盖上述边界,并支持对象、数组及 Map/Set 等集合类型。Proxy 返回新对象,使用时应持续操作代理。
这不意味着 Proxy 自动深度代理,也不意味着一定在所有场景更快;Vue 的深层转换、依赖收集和调度仍需要框架实现并应通过实际测量评价。
四、Vue 3 响应式原理
Vue 3 使用 Proxy、Reflect 和 effect 系统实现响应式。可把过程简化成三个教学阶段:
reactive(raw)创建或复用 raw 对象对应的代理。- effect、computed、watch 或组件渲染读取
state.key时,经get进入track(target, key)收集依赖。 - 写入
state.key时,经set判断操作类型和值是否变化,再由trigger(target, key)调度相关依赖。
import { reactive, watchEffect } from 'vue'
const state = reactive({ count: 0, nested: { value: 1 } })
watchEffect(() => {
console.log(state.count, state.nested.value)
})
state.count++
state.nested.value = 2
原生 Proxy 只代理 target 一层;Vue 在 Reflect.get() 得到嵌套对象时返回其响应式代理,于是形成深层转换。数组还涉及 ADD、SET、DELETE、length 与迭代依赖,不能用“只要是自有属性或新旧值相同就 trigger”概括。Composition API 中通常写 state.key 或 ref.value,不是通用的 this[key]。
五、说说 Vue 3 的 tree-shaking?
Tree-shaking 是 dead-code elimination 的一种构建优化。Vue 3 以 ESM 具名导出提供许多 API:
import { computed, ref } from 'vue'
const count = ref(0)
const double = computed(() => count.value * 2)
支持 tree-shaking 的 bundler 静态分析 ESM 依赖,再删除可证明未使用且无副作用的代码。Vue 提供可裁剪的模块形态,但真正执行分析和删除的是构建工具。
Vue 2 的许多全局 API 集中在单例对象上,较难按属性调用裁剪;也不能绝对说 Vue 2 的任何未用功能都会进入所有生产包。tree-shaking 可能降低下载、解析和执行成本,收益取决于构建配置和代码,应检查实际产物而非承诺固定比例。
六、Vue 3 新特性有哪些?
- 性能与编译优化:Proxy 响应式、patch flags、block tree、静态提升、handler caching 等。
- 更好的 TypeScript 支持:Vue 3 源码和公共类型以 TypeScript 构建,复杂组件与工具链推断通常更好。
- Composition API:
setup()、ref、reactive、computed、watch等可按逻辑组合,但 Options API 仍可使用。 - 新内置能力:
- Fragment:组件支持多个根节点。
- Teleport:把内容渲染到组件 DOM 层级之外的目标节点。
- Suspense:协调异步依赖与 fallback,具体稳定性和适用边界以当前文档为准。
- tree-shaking 友好的 ESM API:效果由 bundler 和实际引用决定。
- Custom Renderer API:可构建非 DOM 渲染器,例如 Canvas、WebGL 或原生平台渲染;它不是“用 DOM 实现 WebGL”。
生命周期顺序应为:setup() → beforeCreate → created → beforeMount → mounted。setup() 在所有 Options API 生命周期之前执行,且其中没有组件 this。
七、组合式 API 生命周期钩子有变化吗?
setup() 取代了在组合式逻辑中使用 beforeCreate、created 的需求。其他常见对应关系为:
beforeMount→onBeforeMountmounted→onMountedbeforeUpdate→onBeforeUpdateupdated→onUpdatedbeforeUnmount→onBeforeUnmountunmounted→onUnmountederrorCaptured→onErrorCaptured
这些钩子必须在 setup() / <script setup> 的同步调用栈中注册,以绑定当前组件实例。setup() 无 this;若同时使用 Options API,setup 返回的绑定可在后续 Options hooks 中通过实例访问。

八、watch 和 watchEffect 的区别?
watch显式指定一个或多个数据源,默认惰性执行,可拿到新旧值并配置immediate、deep等。watchEffect立即运行,自动收集同步执行期间读取的依赖,适合副作用;它没有 new/old 参数,返回值也不作为派生状态。computed返回缓存的派生 ref;watchEffect用于执行副作用。- 异步
watchEffect只跟踪第一个await之前同步读取的依赖。
import { reactive, ref, watch, watchEffect } from 'vue'
const sum = ref(0)
const msg = ref('你好啊')
const person = reactive({
name: '张三',
age: 18,
job: { j1: { salary: 20 } }
})
watch(sum, (newValue, oldValue) => {
console.log(newValue, oldValue)
}, { immediate: true })
watch([sum, msg], ([newSum, newMsg], [oldSum, oldMsg]) => {
console.log(newSum, newMsg, oldSum, oldMsg)
}, { immediate: true })
// 直接 watch reactive 对象时隐式 deep
watch(person, (newValue, oldValue) => {
// 深层就地修改时 newValue === oldValue,因为两者指向同一代理
console.log(newValue, oldValue)
}, { immediate: true })
watch(() => person.name, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
watch(
[() => person.name, () => person.age],
(newValues, oldValues) => console.log(newValues, oldValues)
)
watch(() => person.job, (newValue, oldValue) => {
console.log(newValue, oldValue)
}, { deep: true })
watchEffect(() => {
console.log(sum.value, person.name)
})
watchEffect(async () => {
console.log(sum.value) // 会被跟踪
await Promise.resolve()
console.log(msg.value) // 此次 await 后读取不会被这次运行自动跟踪
})
直接 watch reactive 对象时会隐式进行深层监听。Vue 3.5+ 可用 { deep: false } 将遍历限制在根层;如果要兼容更早版本或只监听明确字段,建议改用 getter。无论哪种方式,深层就地修改都不是快照机制。

九、v-if 和 v-for 哪个优先级高?
Vue 2 同一元素上 v-for 优先;Vue 3 改为 v-if 优先。因此 Vue 3 中同节点的 v-if="!todo.done" 无法读取尚未进入作用域的 todo。
优先使用计算属性过滤:
const visibleTodos = computed(() => todos.value.filter(todo => !todo.done))
也可把 v-for 放到 <template>:
<template v-for="todo in todos" :key="todo.id">
<li v-if="!todo.done">{{ todo.text }}</li>
</template>
十、script setup 是做什么的?
<script setup> 是单文件组件(SFC)的编译期语法糖,顶层绑定可直接用于模板,导入组件也可直接使用,不需要 components 注册。
<script setup lang="ts">
import { useAttrs, useSlots } from 'vue'
const props = defineProps<{ title: string }>()
const emit = defineEmits<{ save: [id: number] }>()
const attrs = useAttrs()
const slots = useSlots()
defineExpose({ save: () => emit('save', 1) })
</script>
defineProps、defineEmits、defineExpose 是编译器宏,无需导入;useAttrs、useSlots 是运行时函数,需要从 vue 导入。组件默认不会通过模板 ref 暴露所有顶层绑定,可用 defineExpose 明确公开。
十一、Vue 2 / Vue 3 组件通信方式?
Vue 3 常见方式:
- props / emits
v-model与update:modelValue,或具名v-model:prop- template ref +
defineExpose $attrs- slots
- provide / inject
- Pinia
- 必要时使用 mitt 等外部事件发射器
provide/inject 沿祖先组件链按 key 查找最近 provider,不是原型链;提供 ref 时注入到脚本中仍是 ref。
Vue 2 历史方式还包括 .sync、$listeners、$children、实例 EventBus 等。迁移到 Vue 3 时:.sync 改为具名 v-model,$listeners 合并进 $attrs,不应依赖私有组件层级 API。Vue 3 移除的是实例 $on、$off、$once,不是组件的声明式 $emit。
十二、ref 与 reactive 的区别?
ref()可接收 primitive 或对象,JavaScript 中通过.value读写;模板顶层 ref 通常自动解包。reactive()只能接收对象类型(对象、数组、Map、Set 等),返回深层响应式 Proxy。ref(primitive)通过自身 getter/setter 跟踪.value;只有 ref 持有对象时,内部对象才会通过reactive()深层转换。因此不能简单说“ref 底层都是 reactive”。- reactive 根对象不宜整体替换,直接解构其 primitive 属性会断开响应式连接;ref 的
.value可以整体替换。 - 数组和集合中的 ref 还有不自动解包等边界。
两者能力有交集但并非“ref 能做 reactive 的一切”。可优先使用 ref 声明状态,再根据对象结构、整体替换和解构需求选择。
十三、EventBus 与 mitt 的区别?
Vue 2 常借助 Vue 实例的 $on/$emit/$off 实现 EventBus。Vue 3 移除了实例 $on/$off/$once,所以不能继续把 Vue 实例当事件总线;组件 $emit 仍然存在。
mitt 是与框架无关的事件发射器,提供 on、emit、off 等 API,也支持 TypeScript 使用。它适合少量跨边界事件,但大量全局事件仍可能难以追踪;应优先考虑 props/emits、provide/inject 或 Pinia。库体积会随版本和构建方式变化,不应写死固定 bytes。
十四、谈谈 Pinia?
Pinia 是 Vue 官方推荐的状态管理库。Options Store 使用 defineStore() 定义 state、getters、actions:
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: state => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
Pinia 没有 mutations,也支持以 Composition API 风格定义 Setup Store。每个 store 有唯一 id,调用 useCounterStore() 时创建或取得实例,通常不需要像 Vuex module 那样向根 store 手动注册;多个 store 可以显式组合。
它支持 TypeScript、Vue DevTools 和 SSR,但需理解边界:
- 普通 SPA 刷新后内存状态消失是预期行为。
- 持久化不是 Pinia 默认能力,可用插件或
$subscribe自行实现,并评估敏感信息和 XSS 风险。 - SSR 需要安全序列化服务端状态,并在客户端使用 store 前完成 hydration,避免跨请求状态污染。
不要把 Pinia 描述为固定压缩体积,也不要依据旧讨论断言“就是 Vuex 5 且绝不会再有其他版本”;以当前 Pinia 和 Vuex 官方文档为准。
原文出处
本文摘录、整理自:
作者:丝绒拿铁有点甜
原文链接:https://juejin.cn/post/7139921537896808479
来源:稀土掘金。著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。