主题
第 13 章 · Vue 入门 面试题集
Q1:Vue 3 的响应式原理是什么?为什么从 defineProperty 换成 Proxy?
考察点:响应式系统、Proxy vs defineProperty
标准答案: Vue 3 使用 ES6 的 Proxy 拦截对象的所有读写操作(get/set/has/deleteProperty 等),实现依赖收集与触发更新:
- get 时调用
track()把当前活跃的 effect(组件渲染函数 / computed / watch)记录到该属性的依赖集合 - set 时调用
trigger()通知所有依赖该属性的 effect 重新执行
相比 Vue 2 的 Object.defineProperty,Proxy 的优势:
- 可以监听新增/删除属性,无需
Vue.set - 可以监听数组索引和 length 修改
- 不需递归预处理整个对象,访问到时再代理(懒处理 + 性能更好)
- 拦截能力更全面(has、ownKeys 等)
通俗理解: defineProperty 像在每扇门上各装一个门铃,门没装就听不见敲门。Proxy 像在整栋楼大门口设了个总门卫,进来出去全都登记。
代码示例:
js
new Proxy(obj, {
get(t, k, r) { track(t, k); return Reflect.get(t, k, r); },
set(t, k, v, r) { const ok = Reflect.set(t, k, v, r); trigger(t, k); return ok; }
});加分回答:
- Proxy 不能监听基本类型 → Vue 3 提供
ref包装,借助 getter/setter 拦截.value - Proxy 兼容性:IE11 不支持,Vue 3 不再支持 IE
- 嵌套对象按需创建子 Proxy,访问时才代理(懒响应)
追问:
- ref 和 reactive 内部分别怎么实现?
- 为什么 reactive 解构会丢失响应性?
Q2:computed、watch、watchEffect 的区别?
考察点:响应式 API 选型
标准答案:
| API | 是否有返回值 | 是否缓存 | 何时执行 | 用途 |
|---|---|---|---|---|
computed | 有(响应式 ref) | 是 | 依赖变化且有人读时 | 派生数据 |
watch(src, cb) | 无 | 无 | 指定源变化时 | 监听变化做副作用,可拿到新旧值 |
watchEffect(fn) | 无 | 无 | 立即执行一次 + 自动追踪依赖 | 副作用,不需新旧值对比 |
通俗理解:
- computed = 算账员(结果给你看,账没变就不重算)
- watch = 监工(盯着某个工人,一动手就吹哨子,知道他从哪一步到哪一步)
- watchEffect = 自动巡逻员(自己摸清要盯哪些人,谁动就过去看)
代码示例:
js
const double = computed(() => count.value * 2);
watch(count, (n, o) => console.log(o, '→', n));
watchEffect(() => console.log('a + b =', a.value + b.value));加分回答:
- computed 的 setter 写法可实现"双向计算属性"
watch默认 lazy,{ immediate: true }立即执行;{ deep: true }深度监听对象watchEffect自动收集依赖,但难以排查"为什么执行了"- 异步副作用要清理:
watchEffect((onCleanup) => { ... onCleanup(() => ...) })
追问:
- computed 的缓存是基于什么实现的?
- 在 watch 里改自己监听的值会死循环吗?
Q3:v-if 和 v-show 的区别?什么时候用哪个?
考察点:渲染机制、性能
标准答案:
v-if:根据条件销毁/创建 DOM 元素(含子组件实例和事件)v-show:始终渲染,仅切换 CSSdisplay属性
| v-if | v-show | |
|---|---|---|
| 实现 | 销毁/创建 | 切换 display |
| 切换开销 | 大 | 小 |
| 初始开销 | false 时为 0 | 始终渲染 |
| 编译条件 | 支持 v-else / v-else-if | 不支持 |
| 适合 | 不常切换、初始可能不显示 | 频繁切换、需要保留状态 |
通俗理解: v-if 像"拆墙"——不要这间房就拆掉,要再建。v-show 像"窗帘"——拉上看不见但房子还在。
加分回答:
- v-show 切换不会触发组件生命周期;v-if 每次切换都触发 mount/unmount
- 用
<template v-if="...">可以条件渲染多个元素而不增加 DOM 节点 - v-if + v-for 同元素是反模式,Vue 3 中 v-if 优先级高于 v-for
追问:
- v-if 与 keep-alive 配合时表现如何?
- v-show 适合所有元素吗?(不适合
<template>)
Q4:Vue 中的 key 有什么作用?
考察点:虚拟 DOM Diff、列表更新
标准答案: key 是 Vue 给每个虚拟 DOM 节点的"唯一标识",用于在 Diff 时识别"同一节点",决定复用、移动、新增、删除。
- 没 key 或
key=index时,Vue 会用"就地复用"策略,可能导致组件状态错位 - 用稳定的业务 id 当 key,能让 Diff 精准、性能更好
通俗理解: key 像学号——按学号点名最准;按"座位号"(index)点名一换座就乱。
代码示例:
html
<li v-for="item in list" :key="item.id">{{ item.name }}</li>加分回答:
- Vue 3 Diff 用了"双端比较 + 最长递增子序列"算法,key 越稳定越能找到最小变更
- 强制重新渲染组件可用
:key变化触发重建:<Comp :key="forceKey" /> - 不能用
Math.random()当 key,每次都不同 = 每次都重建
追问:
- 没加 key 时 Vue 会做什么?
- 用 index 作 key 时,删除中间项会出现什么 bug?
Q5:组件之间如何通信?
考察点:组件间数据流
标准答案:
| 场景 | 方式 |
|---|---|
| 父 → 子 | props |
| 子 → 父 | $emit 事件 |
| 双向 | v-model(语法糖::modelValue + @update:modelValue) |
| 父 → 后代(跨层级) | provide / inject |
| 兄弟 / 任意 | EventBus(不推荐)/ 全局状态库(Pinia) |
| 父调用子方法 | ref + defineExpose |
代码示例:
vue
<!-- 子 -->
<script setup>
const props = defineProps(['msg']);
const emit = defineEmits(['change']);
defineExpose({ doSomething: () => {} });
</script>
<!-- 父 -->
<Child :msg="hello" @change="onChange" ref="childRef" />加分回答:
- Vue 3.4+ 的
defineModel简化了双向绑定 provide/inject默认非响应式,要传ref/reactive才响应- 大型应用优先用 Pinia 管理跨组件状态
追问:
- v-model 的展开形式是什么?多个 v-model 如何写?
- provide 的值在子组件能修改吗?要怎么传约定?
Q6:Composition API 相比 Options API 的优势?
考察点:API 设计哲学、复用模式
标准答案:
- 逻辑聚合:一个功能点的 data/computed/watch/method 集中在一起,而不是按"选项"分散在四个区
- 更好的复用:自定义组合函数
useXxx替代 mixin,无命名冲突、依赖关系清晰 - 更好的 TS 支持:纯函数 + 类型推导,比
this.xxx友好 - 更小的产物:tree-shaking 友好(只引用到的 API 才打包)
- 逻辑更可读:复杂组件不再"上下翻找"
通俗理解: Options API 像"按工种排座位"——所有数据坐一桌,所有方法坐一桌,需要一个功能时要跨桌找人。Composition API 像"按项目排座位"——同一功能的数据、方法、监听都坐在一起。
代码示例:
vue
<script setup>
function useCounter() {
const count = ref(0);
const inc = () => count.value++;
return { count, inc };
}
const { count, inc } = useCounter();
</script>加分回答:
<script setup>是 Composition API 的语法糖,自动暴露顶层变量给模板- Options API 仍然完整保留,且对零基础新手更友好
- React Hooks 启发了 Composition API,但 Vue 解决了 React 闭包陷阱问题
追问:
- setup 函数的执行时机是什么?只执行一次吗?
- Composition API 怎么实现 mixin 的功能?
Q7:Vue 中 ref 和 reactive 怎么选?
考察点:响应式 API 选型
标准答案:
| 维度 | ref | reactive |
|---|---|---|
| 适合 | 基本类型、单值 | 对象、数组 |
| 模板中 | 自动解包,无需 .value | 直接访问 |
| JS 中 | 必须 .value | 直接访问 |
| 解构 | const x = ref(0) 后传递不丢失 | 解构后丢失响应性,需 toRefs |
| 整体替换 | 直接 x.value = newObj | 不能 state = newObj,要 Object.assign 或重新声明 |
实践建议:
- 全部用
ref写,约定一致,不需要纠结 - 如果团队偏好 reactive 写复杂对象,注意解构问题
代码示例:
js
const count = ref(0); // 基本类型
const user = ref({ name:'a' }); // 也行
const state = reactive({ a: 1, b: 2 });
const { a } = toRefs(state); // ✅ a 仍然响应加分回答:
ref内部基于 reactive 实现:ref(obj)会把 obj 转成 reactiveshallowRef/shallowReactive只代理一层,性能更好readonly创建只读代理;isRef/isReactive判断类型
追问:
ref(obj)和reactive(obj)内部一致吗?- 为什么
const { count } = reactive({ count: 0 })会失去响应性?
Q8:Vue 的双向绑定原理?
考察点:v-model、单向数据流的"语法糖"
标准答案: Vue 没有"真正的双向绑定",v-model 是 :value + @input/change 的语法糖:
html
<input v-model="msg" />
<!-- 等价于 -->
<input :value="msg" @input="msg = $event.target.value" />在自定义组件上:
html
<MyInput v-model="name" />
<!-- 等价于 -->
<MyInput :modelValue="name" @update:modelValue="name = $event" />数据流仍然是单向的——只是 Vue 帮你自动写了 setter。
通俗理解: v-model 像"温控器"——你看到的是"温度上下都能调",背后是分开的"读温度"+"按按钮"两个动作,框架帮你按好了。
加分回答:
- 多个 v-model:
<Comp v-model:title="t" v-model:content="c" /> - 修饰符:
.lazy(change 而非 input)、.number、.trim - Vue 3.4 引入
defineModel()简化自定义组件 v-model v-model在不同元素上展开形式不同:input 用value/input,checkbox 用checked/change,select 用value/change
追问:
- 自定义组件如何支持
v-model.modifier? - v-model 和 React 的"受控组件"有什么异同?
Q9:Vue 的生命周期有哪些?常用钩子在什么时机?
考察点:组件生命周期
标准答案:
| Options 钩子 | Composition 钩子 | 时机 |
|---|---|---|
| beforeCreate | — | 实例初始化,data/methods 还没准备好 |
| created | (setup 本身) | data/methods 准备好,但 DOM 未挂载 |
| beforeMount | onBeforeMount | 模板编译完,挂载前 |
| mounted | onMounted | DOM 挂载完成,可访问 $el / DOM 引用 |
| beforeUpdate | onBeforeUpdate | 数据变化,DOM 更新前 |
| updated | onUpdated | DOM 更新后 |
| beforeUnmount | onBeforeUnmount | 卸载前,可清理定时器 |
| unmounted | onUnmounted | 已卸载 |
| errorCaptured | onErrorCaptured | 捕获后代组件错误 |
| activated/deactivated | onActivated/onDeactivated | keep-alive 切换 |
父子顺序:父 created → 父 beforeMount → 子 created → 子 mounted → 父 mounted。
通俗理解:
- created = 怀孕(数据准备好了,"宝宝"还没出来)
- mounted = 出生(看到 DOM 了)
- updated = 长大(数据变了,DOM 也变了)
- unmounted = 离开(清理后事)
代码示例:
js
import { onMounted, onUnmounted } from 'vue';
let timer;
onMounted(() => { timer = setInterval(...) });
onUnmounted(() => clearInterval(timer));加分回答:
- Vue 3 移除了
beforeDestroy/destroyed,改名beforeUnmount/unmounted setup的执行时机相当于 Options 中的beforeCreate+created- SSR 中
mounted不在服务端执行,操作 DOM 的代码要放在 onMounted
追问:
- 父 mounted 和子 mounted 谁先?为什么?
- 在 created 中能访问
this.$refs吗?
Q10:插槽(slot)有哪几种?怎么用?
考察点:组件灵活性、组合模式
标准答案:
- 默认插槽:
<slot />接收父组件传入的默认内容 - 具名插槽:
<slot name="header" />,父用<template #header>填入 - 作用域插槽:子把数据通过
<slot :item="x" />暴露给父,父用<template #default="{ item }">解构使用
代码示例:
vue
<!-- 子组件 -->
<div>
<slot name="header" />
<slot :user="currentUser" />
<slot name="footer" />
</div>
<!-- 父组件 -->
<Child>
<template #header><h1>标题</h1></template>
<template #default="{ user }">{{ user.name }}</template>
<template #footer>...</template>
</Child>通俗理解:
- 插槽 = "挖个坑等你填内容"
- 作用域插槽 = "我(子)做了这道菜(数据),你(父)决定怎么摆盘"
加分回答:
- 插槽内容默认在父组件作用域中渲染(数据来自父)
- 作用域插槽常见场景:通用 List 组件、Table 列定制、Tree 节点渲染
- Vue 3 移除了
slot和slot-scope(Vue 2 旧语法),统一用v-slot/#
追问:
- 作用域插槽和 React 的 render props 有什么类比关系?
- 默认插槽里的内容能访问子组件的数据吗?
Q11:Vue 和 React 的核心区别?
考察点:框架对比
标准答案:
| 维度 | Vue | React |
|---|---|---|
| 语法 | 模板 + 指令(HTML 友好) | JSX(JS 友好) |
| 响应式 | Proxy 自动追踪 | 手动 setState |
| 状态更新 | 改 .value / 直接赋值 | 调用 setter,不可变更新 |
| 重渲染粒度 | 组件级(依赖追踪精确到属性) | 组件级(默认全树重渲) |
| 学习曲线 | 较缓(模板直觉) | 较陡(需理解 Hooks/闭包) |
| 状态管理 | 官方 Pinia | 社区 Redux/Zustand |
| 生态控制 | 官方全家桶强 | 社区方案多元 |
| 国内市场 | 占比高 | 大厂偏好 |
加分回答:
- 设计哲学:Vue "渐进式 + 约定优于配置",React "灵活 + 显式"
- 性能:实战差距小,都很快;Vue 默认更新粒度更细,React 需要 memo
- 团队:Vue 上手快、新人友好;React 生态广、长期演进更激进
追问:
- 你为什么选 Vue / React?
- Vue 能完全用 JSX 写吗?
Q12:Vue 的虚拟 DOM 和 React 有什么不同?
考察点:渲染机制
标准答案:
- 共同点:都用 JS 对象描述 DOM,Diff 后批量更新
- Vue 3 的特别之处:
- 编译时优化:模板编译时给静态节点打 patchFlag,运行时只 diff 动态部分
- Block Tree:把动态节点提到 block 数组,diff 时只遍历这些节点
- 响应式驱动:精确知道哪个组件依赖了哪个属性,一变只重渲染那个组件
- React 因为是"任意函数返回 JSX",无法做模板级编译优化(但 React 编译器在补齐这一点)
通俗理解: Vue 写模板像填表格,框架知道哪些格子是"变量"哪些是"固定字",更新时只擦变量。React 写 JSX 像写自由作文,框架要全文重新比对才知道改了哪。
加分回答:
- Vue 3 性能优化点:静态提升、patchFlag、Block Tree、缓存事件处理器
- React 19 的编译器会自动 memo,缩小这方面差距
- 二者 Diff 复杂度都是 O(n)
追问:
- patchFlag 是什么?举例说明
- 为什么 Vue 能做模板编译优化而 React 难?