Vue 面试题
前端面试题 - Vue.js 篇
本章节收录 Vue.js 相关面试题,涵盖 Vue 2 和 Vue 3 的核心原理、响应式系统、组合式 API、性能优化等高频面试题目。
📊 数据统计
| 统计项 | 数值 |
|---|---|
| 总题数 | 85道 |
| 基础题 | 25道 (29.4%) |
| 中级题 | 40道 (47.1%) |
| 高级题 | 20道 (23.5%) |
📚 目录
一、Vue 基础
1. Vue 2 和 Vue 3 有哪些区别?
难度:⭐⭐(中级)
标签:#Vue2 #Vue3 #对比
参考答案要点:
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 响应式原理 | Object.defineProperty | Proxy |
| API 风格 | Options API | Options API + Composition API |
| 源码语言 | JavaScript | TypeScript |
| 性能 | 一般 | 提升30%-50% |
| Tree-shaking | 不支持 | 支持 |
| 多根节点 | 不支持 | 支持(Fragment) |
| 生命周期 | beforeCreate/created | setup |
| 内置组件 | 无 Teleport/Suspense | 有 Teleport/Suspense |
核心改进:
- 响应式系统:Vue 3 使用 Proxy 替代 Object.defineProperty,解决了数组索引和对象新增属性监听问题
- 编译优化:静态提升(Static Hoisting)+ PatchFlag,减少 Diff 工作量
- Tree-shaking:按需引入,减少打包体积
- Composition API:更好的逻辑复用,适合大型项目
2. 谈谈对 MVVM 模式的理解?
难度:⭐⭐(中级)
标签:#MVVM #架构模式
参考答案要点:
MVVM = Model + View + ViewModel
- Model:数据模型,定义数据和业务逻辑
- View:视图层,UI 展示
- ViewModel:视图模型,连接 Model 和 View
Vue 中的实现:
- Vue 实例作为 ViewModel
- Data 作为 Model
- Template 作为 View
- 通过数据绑定实现自动同步
与 MVC 的区别:
- MVC 是单向通信,Controller 负责转发
- MVVM 是双向绑定,ViewModel 自动同步数据变化
3. Vue 的生命周期有哪些?执行顺序是什么?
难度:⭐⭐(中级)
标签:#生命周期
参考答案要点:
Vue 2 生命周期
创建阶段:
- beforeCreate:实例初始化,数据观测之前
- created:实例创建完成,可访问数据,未挂载 DOM
挂载阶段:
- beforeMount:挂载前,模板编译完成
- mounted:挂载完成,DOM 已创建,可访问 refs
更新阶段:
- beforeUpdate:数据更新,DOM 重新渲染前
- updated:DOM 更新完成
销毁阶段:
- beforeDestroy:实例销毁前,可清理资源
- destroyed:实例销毁完成
Vue 3 生命周期
Options API:与 Vue 2 类似,但销毁钩子改为 beforeUnmount / unmounted
Composition API:
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
setup() {
onBeforeMount(() => {})
onMounted(() => {})
// ...
}4. v-if 和 v-show 的区别?
难度:⭐(基础)
标签:#指令
参考答案要点:
| 特性 | v-if | v-show |
|---|---|---|
| 渲染方式 | 条件为 false 时不渲染 | 始终渲染,只是切换 display |
| 切换开销 | 高(创建/销毁组件) | 低(CSS 切换) |
| 初始渲染 | 可能不渲染 | 始终渲染 |
| 适用场景 | 条件很少改变 | 需要频繁切换 |
注意:v-if 有更高的切换开销,v-show 有更高的初始渲染开销
5. v-for 为什么要配合 key 使用?
难度:⭐⭐(中级)
标签:#列表渲染 #Diff
参考答案要点:
key 的作用:
- 唯一标识:帮助 Vue 识别每个节点的身份
- Diff 优化:在虚拟 DOM Diff 过程中,通过 key 快速找到对应节点,复用已有元素
- 状态保持:避免就地复用导致的组件状态混乱
不使用 key 的问题:
- Vue 默认使用"就地复用"策略
- 列表顺序变化时,可能复用错误的元素
- 表单元素状态会混乱
key 的注意事项:
- 不要使用索引作为 key(除非列表不会变化)
- 使用唯一 ID 作为 key
- key 必须是基本类型(string/number)
二、响应式原理
6. Vue 2 的响应式原理是什么?
难度:⭐⭐⭐(高级)
标签:#响应式 #Object.defineProperty
参考答案要点:
核心机制:数据劫持 + 发布订阅模式
对象响应式
function defineReactive(obj, key, val) {
const dep = new Dep() // 依赖收集器
Object.defineProperty(obj, key, {
get() {
if (Dep.target) {
dep.depend() // 收集依赖
}
return val
},
set(newVal) {
if (newVal !== val) {
val = newVal
dep.notify() // 通知更新
}
},
})
}数组响应式
Vue 2 重写了数组的 7 个方法:
- push/pop/shift/unshift
- splice/sort/reverse
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
[("push", "pop", "shift", "unshift", "splice", "sort", "reverse")].forEach(
(method) => {
const original = arrayProto[method]
Object.defineProperty(arrayMethods, method, {
value(...args) {
const result = original.apply(this, args)
// 通知更新
return result
},
})
}
)局限性
- 无法检测对象属性的新增/删除(需使用 Vue.set/Vue.delete)
- 无法检测数组索引的变化
- 无法检测直接修改数组长度的变化
7. Vue 3 的响应式原理是什么?
难度:⭐⭐⭐(高级)
标签:#响应式 #Proxy
参考答案要点:
核心机制:Proxy + Reflect
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key) // 依赖收集
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (oldValue !== value) {
trigger(target, key) // 触发更新
}
return result
},
deleteProperty(target, key) {
const hadKey = Object.prototype.hasOwnProperty.call(target, key)
const result = Reflect.deleteProperty(target, key)
if (hadKey && result) {
trigger(target, key)
}
return result
},
})
}优势对比:
| 特性 | Vue 2 (defineProperty) | Vue 3 (Proxy) |
|---|---|---|
| 新增属性 | 无法监听 | 自动监听 |
| 删除属性 | 无法监听 | 自动监听 |
| 数组索引 | 无法监听 | 自动监听 |
| 数组长度 | 无法监听 | 自动监听 |
| Map/Set | 不支持 | 支持 |
| 性能 | 初始化遍历所有属性 | 懒代理,按需触发 |
8. computed 和 watch 的区别?
难度:⭐⭐(中级)
标签:#计算属性 #侦听器
参考答案要点:
| 特性 | computed | watch |
|---|---|---|
| 用途 | 计算派生数据 | 监听数据变化执行副作用 |
| 缓存 | 有缓存,依赖不变不重新计算 | 无缓存 |
| 返回值 | 有返回值 | 无返回值 |
| 适用场景 | 模板中需要计算的数据 | 数据变化时执行异步/复杂操作 |
| 立即执行 | 默认立即执行 | 可配置 immediate |
| 深度监听 | 自动深度依赖收集 | 需配置 deep |
computed 使用场景:
const fullName = computed(() => {
return firstName.value + " " + lastName.value
})watch 使用场景:
watch(searchText, (newVal, oldVal) => {
// 执行搜索请求
fetchSearchResults(newVal)
})9. Vue 2 如何检测数组变化?有哪些限制?
难度:⭐⭐⭐(高级)
标签:#响应式 #数组
参考答案要点:
检测方法:
-
重写数组方法:Vue 2 重写了数组的 7 个变异方法
- push/pop/shift/unshift/splice/sort/reverse
-
替换数组:使用非变异方法后替换原数组
JAVASCRIPT// 使用 filter/concat/slice 后赋值 this.items = this.items.filter((item) => item.completed)
限制:
-
不能检测索引变化:
JAVASCRIPT// ❌ 无法检测 this.items[0] = newValue // ✅ 解决方案 this.$set(this.items, 0, newValue) // 或 this.items.splice(0, 1, newValue) -
不能检测长度变化:
JAVASCRIPT// ❌ 无法检测 this.items.length = 0 // ✅ 解决方案 this.items = []
三、虚拟 DOM 与 Diff 算法
10. 什么是虚拟 DOM?有什么优缺点?
难度:⭐⭐(中级)
标签:#虚拟DOM
参考答案要点:
定义:虚拟 DOM 是用 JavaScript 对象描述真实 DOM 结构的树形数据结构。
// 虚拟 DOM 示例
const vnode = {
tag: "div",
props: { id: "app", class: "container" },
children: [
{ tag: "h1", props: {}, children: ["Hello"] },
{ tag: "p", props: {}, children: ["World"] },
],
}优点:
- 性能优化:减少直接操作 DOM,批量更新
- 跨平台:虚拟 DOM 可以映射到不同平台(Web/Native/小程序)
- 开发体验:声明式编程,无需手动操作 DOM
缺点:
- 内存占用:需要额外的内存存储虚拟 DOM
- 首次渲染慢:需要先生成虚拟 DOM
- 不适合简单页面:简单页面直接操作 DOM 可能更快
11. Vue 的 Diff 算法原理是什么?
难度:⭐⭐⭐(高级)
标签:#Diff #算法
参考答案要点:
核心思想:
- 同层比较:只比较同一层级的节点,不跨级比较
- 双端比较:新旧列表从两端向中间比较
- key 优化:通过 key 复用节点
Diff 流程:
-
比较根节点:
- 类型不同:销毁旧节点,创建新节点
- 类型相同:复用节点,比较属性
-
比较子节点:
- 旧节点无子节点,新节点有:添加子节点
- 旧节点有子节点,新节点无:删除子节点
- 都有子节点:执行核心 Diff 算法
-
核心 Diff 算法:
- 使用双指针,从新旧列表两端开始比较
- 找到可复用的节点,移动或更新
- 处理剩余节点(新增或删除)
Vue 2 vs Vue 3 Diff:
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 算法 | 双端比较 | 双端比较 + 最长递增子序列 |
| 静态节点 | 无优化 | 静态提升,跳过 Diff |
| PatchFlag | 无 | 有,标记动态内容 |
12. Vue 3 的静态提升(Static Hoisting)是什么?
难度:⭐⭐⭐(高级)
标签:#编译优化 #性能
参考答案要点:
概念:编译阶段将模板中的静态节点提取出来,避免每次渲染重新创建。
示例:
<template>
<div>
<h1>静态标题</h1>
<!-- 静态节点 -->
<p>{{ dynamic }}</p>
<!-- 动态节点 -->
</div>
</template>编译结果:
const _hoisted_1 = /*#__PURE__*/ createElementVNode(
"h1",
null,
"静态标题",
-1 /* HOISTED */
)
export function render(_ctx, _cache) {
return (
openBlock(),
createElementBlock("div", null, [
_hoisted_1, // 直接复用静态节点
createElementVNode(
"p",
null,
toDisplayString(_ctx.dynamic),
1 /* TEXT */
),
])
)
}优势:
- 减少内存分配
- 减少 Diff 工作量
- 提升渲染性能
四、组件与通信
13. Vue 组件间通信有哪些方式?
难度:⭐⭐(中级)
标签:#组件通信
参考答案要点:
| 方式 | 适用场景 | 示例 |
|---|---|---|
| Props/$emit | 父子组件 | 父传子 props,子传父 emit |
| $refs | 父组件调用子组件方法 | this.$refs.child.method() |
| children | 直接访问父/子实例 | 不推荐,耦合度高 |
| Provide/Inject | 跨多级组件 | 祖先 provide,后代 inject |
| EventBus | 任意组件(Vue 2) | new Vue() 作为中央事件总线 |
| Vuex/Pinia | 全局状态管理 | 复杂应用推荐 |
| listeners | 隔代传参(Vue 2) | 透传属性和事件 |
| v-model | 双向绑定 | 语法糖,本质是 props + emit |
Provide/Inject 示例:
// 祖先组件
provide("user", user)
// 后代组件
const user = inject("user")14. 如何实现自定义指令?
难度:⭐⭐(中级)
标签:#自定义指令
参考答案要点:
Vue 2
Vue.directive("focus", {
inserted(el) {
el.focus()
},
})Vue 3
const vFocus = {
mounted(el) {
el.focus()
}
}
// 使用
<input v-focus />指令钩子:
created:绑定元素属性前beforeMount:元素挂载前mounted:元素挂载后beforeUpdate:更新前updated:更新后beforeUnmount:卸载前unmounted:卸载后
15. 什么是插槽(Slot)?有哪些类型?
难度:⭐⭐(中级)
标签:#插槽
参考答案要点:
插槽类型:
-
默认插槽:
VUE<!-- 子组件 --> <slot>默认内容</slot> <!-- 父组件 --> <Child>插入的内容</Child> -
具名插槽:
VUE<!-- 子组件 --> <slot name="header"></slot> <slot name="footer"></slot> <!-- 父组件 --> <Child> <template #header>头部</template> <template #footer>底部</template> </Child> -
作用域插槽:
VUE<!-- 子组件 --> <slot :user="user"></slot> <!-- 父组件 --> <Child v-slot="{ user }"> {{ user.name }} </Child>
五、Composition API
16. Options API 和 Composition API 的区别?
难度:⭐⭐(中级)
标签:#CompositionAPI #OptionsAPI
参考答案要点:
| 特性 | Options API | Composition API |
|---|---|---|
| 代码组织 | 按选项(data/methods/computed) | 按逻辑功能 |
| 逻辑复用 | Mixins(命名冲突、来源不明) | Composable 函数 |
| TypeScript | 类型推断差 | 类型友好 |
| 代码量 | 较少 | 稍多 |
| 学习曲线 | 平缓 | 较陡 |
Composition API 优势:
- 逻辑复用:通过函数抽取可复用逻辑
- 代码组织:相关逻辑放在一起,易于维护
- Tree-shaking:更好的代码优化
示例对比:
// Options API
export default {
data() {
return { count: 0 }
},
methods: {
increment() {
this.count++
}
}
}
// Composition API
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
}
}17. setup 函数的执行时机和注意点?
难度:⭐⭐(中级)
标签:#setup
参考答案要点:
执行时机:
- 在组件实例创建之前执行
- 在 beforeCreate 和 created 钩子之前
- 此时组件实例还未创建,不能访问 this
参数:
setup(props, context) {
// props:响应式的 props 对象
// context:{ attrs, slots, emit, expose }
}注意点:
- 无 this:setup 中不能使用 this
- 同步执行:setup 必须是同步函数
- 返回值:返回的对象可在模板中使用
- 响应式:需使用 ref/reactive 创建响应式数据
18. ref 和 reactive 的区别?
难度:⭐⭐(中级)
标签:#响应式 #ref #reactive
参考答案要点:
| 特性 | ref | reactive |
|---|---|---|
| 数据类型 | 任意类型 | 对象/数组 |
| 访问方式 | .value | 直接访问属性 |
| 模板使用 | 自动解包 | 直接访问 |
| 替换对象 | 可以替换整个对象 | 不能替换(会失去响应式) |
使用建议:
- ref:基础类型、需要替换的对象、组件引用
- reactive:复杂对象、表单数据
示例:
const count = ref(0)
console.log(count.value) // 0
const state = reactive({ name: "Vue", count: 0 })
console.log(state.name) // Vue19. 什么是自定义 Hook(Composable)?
难度:⭐⭐⭐(高级)
标签:#Composable #逻辑复用
参考答案要点:
定义:将组件逻辑抽取为可复用的函数,以 use 开头命名。
示例:
// useMouse.js
import { ref, onMounted, onUnmounted } from "vue"
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener("mousemove", update))
onUnmounted(() => window.removeEventListener("mousemove", update))
return { x, y }
}
// 使用
import { useMouse } from "./useMouse"
export default {
setup() {
const { x, y } = useMouse()
return { x, y }
},
}优势:
- 逻辑复用,避免 Mixins 的问题
- 类型友好
- 可测试性强
六、Vue Router
20. Vue Router 的导航守卫有哪些?
难度:⭐⭐(中级)
标签:#路由 #导航守卫
参考答案要点:
全局守卫:
beforeEach:路由切换前beforeResolve:解析守卫afterEach:路由切换后
路由独享守卫:
beforeEnter:进入路由前
组件内守卫:
beforeRouteEnter:进入组件前(无 this)beforeRouteUpdate:当前路由改变但组件复用时beforeRouteLeave:离开组件前
执行顺序:
- 全局 beforeEach
- 路由独享 beforeEnter
- 组件内 beforeRouteEnter
- 全局 beforeResolve
- 全局 afterEach
21. hash 模式和 history 模式的区别?
难度:⭐⭐(中级)
标签:#路由模式
参考答案要点:
| 特性 | hash 模式 | history 模式 |
|---|---|---|
| URL 格式 | /#/path | /path |
| 兼容性 | IE8+ | IE10+ |
| 服务端支持 | 不需要 | 需要(所有路由指向 index.html) |
| SEO | 较差 | 较好 |
| 实现原理 | hashchange 事件 | history.pushState/popstate |
history 模式服务端配置:
# Nginx
location / {
try_files $uri $uri/ /index.html;
}七、状态管理
22. Vuex 和 Pinia 的区别?
难度:⭐⭐(中级)
标签:#Vuex #Pinia #状态管理
参考答案要点:
| 特性 | Vuex 4 | Pinia |
|---|---|---|
| Vue 版本 | Vue 2/3 | Vue 2/3 |
| API | Options API | Composition API |
| TypeScript | 类型复杂 | 类型友好 |
| 代码量 | 较多(mutations/actions) | 较少(直接修改 state) |
| 模块 | 需要命名空间 | 自动模块化 |
| 体积 | 较大 | 较小(1KB) |
| 开发体验 | 一般 | 更好(DevTools 支持) |
Pinia 示例:
import { defineStore } from "pinia"
export const useCounterStore = defineStore("counter", {
state: () => ({ count: 0 }),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})23. Vuex 的核心概念有哪些?
难度:⭐⭐(中级)
标签:#Vuex
参考答案要点:
核心概念:
-
State:存储应用状态
JAVASCRIPTstate: { count: 0, user: null } -
Getter:计算属性
JAVASCRIPTgetters: { doubleCount: (state) => state.count * 2 } -
Mutation:同步修改状态
JAVASCRIPTmutations: { increment(state) { state.count++ } } -
Action:异步操作
JAVASCRIPTactions: { async fetchUser({ commit }) { const user = await api.getUser() commit('setUser', user) } } -
Module:模块化管理
JAVASCRIPTmodules: { user: userModule, cart: cartModule }
八、性能优化
24. Vue 项目有哪些性能优化手段?
难度:⭐⭐⭐(高级)
标签:#性能优化
参考答案要点:
编码层面:
- v-show 替代 v-if:频繁切换使用 v-show
- v-for 使用 key:避免使用索引作为 key
- computed 缓存:复杂计算使用 computed
- 函数式组件:无状态组件使用函数式组件
- keep-alive:缓存组件状态
- 异步组件:路由懒加载
构建层面:
-
路由懒加载:
JAVASCRIPTconst Home = () => import("./views/Home.vue") -
Tree-shaking:按需引入组件
-
代码分割:动态导入
-
Gzip/Brotli 压缩
运行时层面:
- 虚拟滚动:大数据列表使用虚拟滚动
- 防抖节流:事件处理
- SSR/SSG:首屏渲染优化
25. 什么是 keep-alive?有什么作用?
难度:⭐⭐(中级)
标签:#keep-alive #组件缓存
参考答案要点:
作用:缓存组件实例,避免重复渲染,保留组件状态。
使用:
<keep-alive :include="['Home', 'User']" :exclude="['Detail']" :max="10">
<component :is="currentComponent" />
</keep-alive>属性:
include:指定缓存的组件名(数组/字符串/正则)exclude:指定不缓存的组件名max:最大缓存实例数(LRU 淘汰)
生命周期:
activated:组件激活时调用deactivated:组件停用时调用
适用场景:
- 标签页切换
- 列表页进入详情页后返回保留状态
九、复习建议
重点题目清单
必会题目(⭐⭐⭐ 高频)
- Vue 2 和 Vue 3 的区别(响应式原理、API 风格)
- 响应式原理(Object.defineProperty vs Proxy)
- 虚拟 DOM 和 Diff 算法
- computed 和 watch 的区别
- 组件间通信方式
重点掌握(⭐⭐ 常考)
- 生命周期(Vue 2 vs Vue 3)
- v-if 和 v-show 的区别
- v-for 的 key 作用
- Composition API(setup、ref、reactive)
- Vue Router(导航守卫、hash vs history)
加分项(⭐ 了解)
- Vue 3 编译优化(静态提升、PatchFlag)
- 自定义 Hook
- Vuex vs Pinia
- 性能优化手段
与 React 对比
面试中经常对比 Vue 和 React,建议准备:
- 响应式 vs 手动 setState
- 模板 vs JSX
- Options API vs Hooks
- Vuex vs Redux
- 双向绑定 vs 单向数据流