Vue 面试笔记
1. Vue 基础认知
1.1 Vue 是什么
Vue 是一个渐进式 JavaScript 框架,核心能力是声明式渲染、组件化开发和响应式数据管理。
它主要解决三个问题:
- 手动操作 DOM 成本高
- 数据和视图同步困难
- 大型前端页面难维护、难复用
面试表达可以这样说:
Vue 本质上是一个以响应式系统为基础的前端框架,通过组件化和声明式渲染,把“数据变化驱动视图更新”这件事标准化了,降低了复杂页面开发和维护成本。
1.2 什么是渐进式
“渐进式”指的是 Vue 可以按需使用:
- 只用核心库做视图层
- 配合 Vue Router 做单页应用
- 配合 Pinia / Vuex 做状态管理
- 配合工程化工具搭建完整前端项目
它不是一上来就强制你接受完整技术栈。
1.3 Vue 的核心特性
- 响应式数据绑定
- 组件化开发
- 模板语法和指令系统
- 生命周期管理
- 虚拟 DOM
- 完整生态支持
1.4 MVVM 的理解
MVVM 包括:
- Model:数据层
- View:视图层
- ViewModel:连接视图和数据的中间层
Vue 和 MVVM 思想接近。开发者操作数据,Vue 负责把数据变化同步到视图。
注意:
- Vue 不是严格教科书意义上的 MVVM
- 但面试里一般回答“Vue 借鉴了 MVVM 思想”就够了
1.5 Vue2 和 Vue3 的区别
核心区别:
- Vue2 用
Object.defineProperty做响应式 - Vue3 用
Proxy做响应式 - Vue3 提供 Composition API
- Vue3 对 TypeScript 支持更好
- Vue3 编译优化更强,性能更好
- Vue3 更适合大型项目逻辑复用
高频一句话:
Vue3 最大的升级是响应式底层和组合式 API,这两点分别提升了性能上限和工程可维护性。
1.6 为什么要有 Composition API
Options API 在小组件里问题不大,但在复杂组件里,同一业务逻辑会被拆散在:
datacomputedmethodswatch- 生命周期
Composition API 的优势:
- 相关逻辑可以写在一起
- 更方便抽离复用为
composable - 类型推导更友好
- 更适合复杂场景和大型项目
2. 模板、指令与语法
2.1 v-if 和 v-show 的区别
v-if:
- 真正控制节点是否创建和销毁
- 初始条件为 false 时不渲染
- 切换成本高
v-show:
- 始终渲染节点
- 通过
display: none控制显示隐藏 - 初始渲染成本高,切换成本低
使用建议:
- 高频切换用
v-show - 条件很少变化用
v-if
常见追问:v-if 和 v-for 为什么不建议一起用?
- 因为
v-for优先级高于v-if - 每次都会先遍历再判断,性能和可读性都差
- 应该先在计算属性中过滤数据
2.2 v-for 为什么要加 key
key 的本质是 vnode 的身份标识。
作用:
- 帮助 diff 正确识别节点
- 提高节点复用准确性
- 避免局部状态错乱
为什么不建议用 index 作为 key:
- 当列表发生插入、删除、排序时,index 会变化
- Vue 可能错误复用 DOM 或组件实例
- 容易出现输入框错位、选中状态错乱等问题
2.3 v-model 的本质
v-model 是语法糖。
原生表单元素上,本质上等价于:
<input :value="msg" @input="msg = $event.target.value" />
在 Vue3 组件上,本质上是:
modelValueupdate:modelValue
即:
<Child :modelValue="value" @update:modelValue="value = $event" />
2.4 常见指令
v-bind:绑定属性,简写:v-on:绑定事件,简写@v-model:双向绑定语法糖v-if:条件渲染v-show:条件显示v-for:列表渲染v-html:插入 HTML,注意 XSS 风险v-text:插入纯文本v-once:只渲染一次v-pre:跳过编译v-slot:插槽语法
2.5 computed 和 watch 的区别
computed:
- 用于派生状态
- 有缓存
- 依赖不变不会重新计算
watch:
- 用于处理副作用
- 没有返回派生值的语义
- 适合做异步请求、日志、联动操作
面试里的标准区分:
能用
computed就不要用watch。computed解决“算出一个值”,watch解决“值变了以后做一件事”。
3. 组件通信
3.1 父子组件通信
常见方式:
- 父传子:
props - 子传父:
emit - 双向绑定:
v-model - 父调用子:
ref+defineExpose - 内容分发:插槽
3.2 兄弟组件通信
推荐方式:
- 把共享状态提升到共同父组件
- 通过父组件中转
- 使用 Pinia / Vuex 统一管理共享状态
不推荐长期使用事件总线,因为:
- 依赖关系不清晰
- 调试困难
- 项目大了以后维护成本高
3.3 为什么 props 是单向数据流
props 的数据流方向是父到子。
这样设计的目的:
- 保证数据来源清晰
- 避免子组件偷偷修改父组件状态
- 降低维护复杂度
如果子组件要修改父级数据,应通过:
emitv-model
3.4 插槽的理解
插槽本质上是父组件向子组件传递一段模板内容。
分类:
- 默认插槽:一个默认内容区域
- 具名插槽:多个内容区域
- 作用域插槽:子组件把内部数据暴露给父组件使用
一句话理解:
插槽解决的是“组件结构可复用,但局部内容需要由外部定制”的问题。
3.5 provide/inject 的使用场景
适合:
- 跨层级传值
- 祖先组件向后代组件提供上下文
- 组件库内部共享配置
不适合:
- 替代全局状态管理
- 大规模业务状态同步
4. 生命周期
4.1 Vue 常见生命周期
Vue2 常见生命周期:
beforeCreatecreatedbeforeMountmountedbeforeUpdateupdatedbeforeDestroydestroyed
Vue3 常用写法:
setuponBeforeMountonMountedonBeforeUpdateonUpdatedonBeforeUnmountonUnmounted
4.2 各阶段适合做什么
created / setup:
- 初始化数据
- 初始化逻辑
- 发起请求也可以,但此时拿不到真实 DOM
mounted:
- 操作真实 DOM
- 初始化第三方库实例
- 获取元素尺寸
beforeUnmount / onUnmounted:
- 清理定时器
- 清理监听器
- 销毁第三方实例
- 断开连接
4.3 created 和 mounted 的区别
created:
- 响应式数据已经建立
- 方法和数据都可用
- DOM 还没有挂载
mounted:
- DOM 已挂载完成
- 可以访问真实 DOM
4.4 为什么不能在 updated 里随便改数据
因为 updated 是数据变化导致视图更新后触发的。
如果你在这里再次修改响应式数据,很容易形成:
- 再次更新
- 再次触发
updated - 死循环
4.5 组件销毁时清理什么
setTimeout/setInterval- DOM 事件监听
- WebSocket / SSE
- 第三方图表或编辑器实例
- 未完成请求的取消逻辑
5. 响应式原理
5.1 Vue2 响应式原理
Vue2 通过 Object.defineProperty 劫持对象已有属性的 get 和 set。
核心流程:
- 初始化时递归遍历对象属性
- 为每个属性定义 getter / setter
- getter 里收集依赖
- setter 里通知依赖更新
核心名词:
Dep:依赖收集器Watcher:观察者,负责响应更新
缺点:
- 不能直接监听属性新增和删除
- 对数组支持不够自然
- 初始化递归成本高
5.2 Vue3 响应式原理
Vue3 通过 Proxy 代理整个对象。
核心流程:
- 代理对象访问
- 读取时
track收集依赖 - 修改时
trigger派发更新
优势:
- 能监听新增和删除属性
- 能更自然处理数组
- 支持
Map、Set - 不需要一开始递归劫持所有属性
5.3 Vue2 为什么对新增属性和数组下标不友好
因为 Vue2 在初始化阶段只劫持已经存在的属性。
所以:
- 后加的属性没有 getter / setter
- 直接通过索引改数组元素,不能完全触发预期更新
Vue2 常见处理方式:
Vue.set(obj, key, value)this.$set(...)- 使用数组变异方法如
splice
5.4 nextTick 是什么
Vue 的 DOM 更新不是同步立即执行的,而是异步批量更新。
nextTick 的作用:
- 等待当前这轮 DOM 更新结束
- 在更新后的时机执行逻辑
典型场景:
- 修改数据后立即获取最新 DOM
- 列表更新后滚动到底部
- 弹窗显示后获取元素尺寸
5.5 为什么 Vue 要异步批量更新
如果每次数据变化都马上更新 DOM,会造成大量重复渲染。
异步批量更新的价值:
- 合并多次更新
- 避免重复操作 DOM
- 提升性能
5.6 ref 和 reactive 的区别
ref:
- 常用于基本类型
- 也可以包对象
- 通过
.value访问
reactive:
- 只能代理对象
- 不能直接代理基本类型
补充:
- 模板里
ref会自动解包 - JS 逻辑里一般需要
.value
5.7 watch 和 watchEffect
watch:
- 明确指定监听源
- 可以拿到新值和旧值
- 可以懒执行
- 可控制深度、时机
watchEffect:
- 自动收集依赖
- 会立即执行一次
- 更适合快速建立副作用
使用建议:
- 依赖源明确时优先
watch - 不想手动列依赖时用
watchEffect
6. 渲染机制与性能优化
6.1 Vue 的 diff 算法
Vue 使用虚拟 DOM。
当数据变化时,会生成新的 vnode 树,再和旧 vnode 树做比较,最后把差异更新到真实 DOM。
核心特点:
- 同层比较,不跨层级暴力对比
- 借助
key判断节点身份 - 尽量做最小化更新
6.2 key 为什么重要
key 影响两件事:
- diff 的正确性
- diff 的效率
如果 key 不稳定:
- 可能错误复用节点
- 组件内部状态会混乱
- 列表操作时更容易出 bug
6.3 Vue3 编译优化做了什么
Vue3 不是纯运行时 diff,它有编译期优化。
重点包括:
- 静态提升:静态节点提升到渲染函数外,避免重复创建
- Patch Flag:标记动态部分,更新时只看重点
- Block Tree:把动态节点组织起来,减少无效遍历
一句话总结:
Vue3 会在编译阶段提前告诉运行时“哪里会变”,从而减少无意义的 diff。
6.4 常见性能优化手段
- 合理拆分组件,减少大组件更新范围
- 列表使用稳定
key - 避免模板中写复杂逻辑和函数调用
- 多用
computed,少做不必要watch - 路由懒加载、组件异步加载
- 大列表使用虚拟滚动
- 高频输入场景做防抖节流
- 减少深层响应式对象和深度监听
- 对纯静态内容用
v-once
6.5 组件重复渲染怎么排查
常见原因:
- 父组件频繁更新导致子组件被动刷新
- 模板里调用函数,每次渲染都执行
watch使用不当- 大对象整体响应式,导致依赖范围过大
排查思路:
- 看组件依赖了哪些响应式数据
- 看父组件是否传入了频繁变化的对象或函数
- 用 Vue Devtools 观察更新链路
7. 组件设计与项目实践
7.1 Vue 项目如何做权限控制
权限一般分三层:
- 路由级权限
- 页面内容级权限
- 按钮级权限
实际原则:
- 前端负责体验控制
- 后端负责真实鉴权
也就是说:
- 前端隐藏按钮不能代表接口安全
- 核心校验一定要在服务端
7.2 路由守卫有哪些
常见路由守卫:
- 全局前置守卫
beforeEach - 全局解析守卫
beforeResolve - 全局后置守卫
afterEach - 路由独享守卫
beforeEnter - 组件内守卫
常见用途:
- 登录校验
- 权限判断
- 页面跳转前数据预处理
- 页面离开拦截
7.3 为什么需要 Pinia / Vuex
当状态需要跨多个组件共享时,如果一直靠 props 和 emit,会出现:
- 传值链路很长
- 组件耦合严重
- 状态流向不清晰
状态管理工具的价值:
- 集中式管理共享状态
- 统一修改入口
- 方便调试和维护
7.4 Pinia 和 Vuex 的区别
Pinia 的特点:
- API 更简洁
- 更贴近 Vue3
- TypeScript 支持更自然
- 没有
mutation - 模块化定义更直观
面试回答可以简化为:
Vuex 更偏传统 Flux 风格,Pinia 更轻量,API 更现代,也是 Vue 官方当前更推荐的方案。
7.5 如何封装可复用组件
核心原则:
- 输入清晰:
props - 输出清晰:
emit - 职责单一
- 低耦合
- 可扩展
常见设计点:
- 是否支持插槽扩展
- 是否支持受控 / 非受控模式
- 默认值是否合理
- 是否提供文档和示例
7.6 如何设计表单组件体系
常见设计思路:
- 字段配置化
- 表单值统一管理
- 校验规则统一抽象
- 支持联动显示和联动校验
- 支持重置、提交、回显
如果是企业项目,通常还会考虑:
- 动态表单
- 权限控制
- 国际化
- 大表单性能
7.7 大列表和复杂表格如何优化
核心手段:
- 分页
- 虚拟列表
- 分块渲染
- 懒加载
- 减少整表级响应式更新
- 只更新变化行
表格场景还要注意:
- 冻结列成本
- 合并单元格成本
- 自定义渲染函数性能
8. 源码理解与高频深问
8.1 computed 为什么有缓存
因为 computed 本质上是基于响应式依赖的惰性求值。
只有依赖变化时才会重新计算,否则直接返回上次结果。
底层关键点:
- 依赖收集
- 脏值标记
- 访问时再求值
8.2 watch 的 deep: true 为什么性能差
因为深度监听通常要递归遍历对象内部所有层级,确保每个深层属性都被访问到。
对象越大、层级越深,成本越高。
所以要避免:
- 对超大对象无脑
deep: true - 明明只关心一个字段,却监听整个对象
8.3 为什么不推荐模板里写复杂表达式
原因有两个:
- 可读性差
- 每次渲染都要重新执行
尤其是模板里直接调函数,会在每次重新渲染时执行,不利于性能和维护。
更推荐:
- 用
computed - 把逻辑抽到方法里,但不要在模板高频调用复杂方法
8.4 组件从创建到销毁的大致过程
- 创建组件实例
- 初始化
props、状态、方法 - 建立响应式联系
- 编译模板为渲染函数
- 挂载并生成真实 DOM
- 运行过程中响应式依赖收集
- 数据变化触发重新渲染
- 组件卸载时清理副作用和资源
8.5 Vue3 为什么用 Proxy
主要是因为 Proxy 的拦截能力更完整。
相比 Object.defineProperty,它更适合处理:
- 新增属性
- 删除属性
- 数组索引
Map/Set
同时它是对整个对象做代理,而不是递归劫持每一个属性,设计上更现代。
8.6 composable 和 mixin 的区别
mixin 的问题:
- 来源不明确
- 命名容易冲突
- 多个 mixin 合并后维护困难
composable 的优势:
- 逻辑来源清楚
- 可按需组合
- 参数传递更自然
- 类型支持更友好
一句话:
Vue3 更推荐用
composable复用逻辑,而不是继续堆mixin。
8.7 什么叫运行时和编译时协同优化
Vue3 的优化不是只靠运行时。
它会在模板编译阶段提前分析:
- 哪些内容是静态的
- 哪些节点是动态的
- 哪些属性会变化
然后把这些信息交给运行时,运行时就能更精准地更新。
9. 项目开放题答题方向
9.1 项目里遇到过哪些 Vue 性能问题
可以从这些方向答:
- 大列表渲染卡顿
- 表单联动过多导致频繁更新
- 页面初次加载慢
- 组件复用不当导致重复渲染
- 大对象深度监听导致卡顿
回答结构建议:
- 先说问题现象
- 再说定位过程
- 最后说解决方案和结果
9.2 封装过哪些组件
可以答:
- 通用表格组件
- 表单组件
- 弹窗组件
- 上传组件
- 权限按钮组件
- 搜索筛选组件
- 图表组件封装
重点不是“列名字”,而是说清楚:
- 组件解决了什么问题
- 输入输出怎么设计
- 如何兼顾通用性和业务灵活性
9.3 做过哪些工程化建设
常见回答方向:
- 路由和权限体系
- 请求封装
- 统一错误处理
- 全局状态管理规范
- 组件库 / 公共组件沉淀
- ESLint / Prettier / Husky 规范
- 自动部署和环境配置
- 性能监控和埋点体系
9.4 从 0 到 1 搭建 Vue 后台项目怎么做
常见回答结构:
- 先选技术栈:Vue3 + Vite + Pinia + Vue Router + TS
- 规划目录结构和模块边界
- 搭建请求层、路由层、权限层
- 统一布局和基础组件
- 建立状态管理和公共 hooks
- 接入规范、构建、环境配置
- 最后补监控、测试、发布流程
9.5 面试时怎么回答“你对 Vue 最薄弱的部分是什么”
不要答得太危险。
推荐答法:
- 可以说“源码细节还在持续深入”
- 可以说“复杂编译优化和运行时细节还在结合项目理解”
- 然后补一句“但常规开发、性能优化和组件设计这部分能独立承担”
10. 面试速记版
10.1 高频一句话结论
- Vue 是一个以响应式系统和组件化为核心的渐进式框架
v-if适合低频切换,v-show适合高频切换key的本质是节点身份标识,不能乱用 indexcomputed解决派生值,watch解决副作用- Vue2 用
Object.defineProperty,Vue3 用Proxy nextTick用于拿到更新后的 DOM- Vue 更新是异步批处理,为了减少重复渲染
- Pinia 是 Vue3 时代更推荐的状态管理方案
composable比mixin更清晰、更易维护- Vue3 的性能提升来自响应式升级和编译优化
10.2 高频易错点
- 不要把
watch和computed混着答 - 不要说
v-model就等于双向绑定本身,要补一句“它是语法糖” - 不要说前端权限控制就能保证安全,要强调以后端鉴权为准
- 不要把
nextTick说成“异步延迟函数”,它的重点是“等待 DOM 更新完成” - 不要只说 Vue3 性能更好,要说出原因:
Proxy、编译优化、静态提升、Patch Flag
10.3 推荐背诵顺序
- Vue 是什么
- Vue2 和 Vue3 区别
v-ifvsv-showcomputedvswatch- 父子组件通信
- 生命周期
- Vue2 / Vue3 响应式原理
nextTick- diff 和
key - Pinia、权限、组件封装、性能优化