跳到主要内容

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 在小组件里问题不大,但在复杂组件里,同一业务逻辑会被拆散在:

  • data
  • computed
  • methods
  • watch
  • 生命周期

Composition API 的优势:

  • 相关逻辑可以写在一起
  • 更方便抽离复用为 composable
  • 类型推导更友好
  • 更适合复杂场景和大型项目

2. 模板、指令与语法

2.1 v-ifv-show 的区别

v-if

  • 真正控制节点是否创建和销毁
  • 初始条件为 false 时不渲染
  • 切换成本高

v-show

  • 始终渲染节点
  • 通过 display: none 控制显示隐藏
  • 初始渲染成本高,切换成本低

使用建议:

  • 高频切换用 v-show
  • 条件很少变化用 v-if

常见追问:v-ifv-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 组件上,本质上是:

  • modelValue
  • update: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 computedwatch 的区别

computed

  • 用于派生状态
  • 有缓存
  • 依赖不变不会重新计算

watch

  • 用于处理副作用
  • 没有返回派生值的语义
  • 适合做异步请求、日志、联动操作

面试里的标准区分:

能用 computed 就不要用 watchcomputed 解决“算出一个值”,watch 解决“值变了以后做一件事”。


3. 组件通信

3.1 父子组件通信

常见方式:

  • 父传子:props
  • 子传父:emit
  • 双向绑定:v-model
  • 父调用子:ref + defineExpose
  • 内容分发:插槽

3.2 兄弟组件通信

推荐方式:

  • 把共享状态提升到共同父组件
  • 通过父组件中转
  • 使用 Pinia / Vuex 统一管理共享状态

不推荐长期使用事件总线,因为:

  • 依赖关系不清晰
  • 调试困难
  • 项目大了以后维护成本高

3.3 为什么 props 是单向数据流

props 的数据流方向是父到子。

这样设计的目的:

  • 保证数据来源清晰
  • 避免子组件偷偷修改父组件状态
  • 降低维护复杂度

如果子组件要修改父级数据,应通过:

  • emit
  • v-model

3.4 插槽的理解

插槽本质上是父组件向子组件传递一段模板内容。

分类:

  • 默认插槽:一个默认内容区域
  • 具名插槽:多个内容区域
  • 作用域插槽:子组件把内部数据暴露给父组件使用

一句话理解:

插槽解决的是“组件结构可复用,但局部内容需要由外部定制”的问题。

3.5 provide/inject 的使用场景

适合:

  • 跨层级传值
  • 祖先组件向后代组件提供上下文
  • 组件库内部共享配置

不适合:

  • 替代全局状态管理
  • 大规模业务状态同步

4. 生命周期

4.1 Vue 常见生命周期

Vue2 常见生命周期:

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeDestroy
  • destroyed

Vue3 常用写法:

  • setup
  • onBeforeMount
  • onMounted
  • onBeforeUpdate
  • onUpdated
  • onBeforeUnmount
  • onUnmounted

4.2 各阶段适合做什么

created / setup

  • 初始化数据
  • 初始化逻辑
  • 发起请求也可以,但此时拿不到真实 DOM

mounted

  • 操作真实 DOM
  • 初始化第三方库实例
  • 获取元素尺寸

beforeUnmount / onUnmounted

  • 清理定时器
  • 清理监听器
  • 销毁第三方实例
  • 断开连接

4.3 createdmounted 的区别

created

  • 响应式数据已经建立
  • 方法和数据都可用
  • DOM 还没有挂载

mounted

  • DOM 已挂载完成
  • 可以访问真实 DOM

4.4 为什么不能在 updated 里随便改数据

因为 updated 是数据变化导致视图更新后触发的。

如果你在这里再次修改响应式数据,很容易形成:

  • 再次更新
  • 再次触发 updated
  • 死循环

4.5 组件销毁时清理什么

  • setTimeout / setInterval
  • DOM 事件监听
  • WebSocket / SSE
  • 第三方图表或编辑器实例
  • 未完成请求的取消逻辑

5. 响应式原理

5.1 Vue2 响应式原理

Vue2 通过 Object.defineProperty 劫持对象已有属性的 getset

核心流程:

  1. 初始化时递归遍历对象属性
  2. 为每个属性定义 getter / setter
  3. getter 里收集依赖
  4. setter 里通知依赖更新

核心名词:

  • Dep:依赖收集器
  • Watcher:观察者,负责响应更新

缺点:

  • 不能直接监听属性新增和删除
  • 对数组支持不够自然
  • 初始化递归成本高

5.2 Vue3 响应式原理

Vue3 通过 Proxy 代理整个对象。

核心流程:

  1. 代理对象访问
  2. 读取时 track 收集依赖
  3. 修改时 trigger 派发更新

优势:

  • 能监听新增和删除属性
  • 能更自然处理数组
  • 支持 MapSet
  • 不需要一开始递归劫持所有属性

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 refreactive 的区别

ref

  • 常用于基本类型
  • 也可以包对象
  • 通过 .value 访问

reactive

  • 只能代理对象
  • 不能直接代理基本类型

补充:

  • 模板里 ref 会自动解包
  • JS 逻辑里一般需要 .value

5.7 watchwatchEffect

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

当状态需要跨多个组件共享时,如果一直靠 propsemit,会出现:

  • 传值链路很长
  • 组件耦合严重
  • 状态流向不清晰

状态管理工具的价值:

  • 集中式管理共享状态
  • 统一修改入口
  • 方便调试和维护

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 watchdeep: true 为什么性能差

因为深度监听通常要递归遍历对象内部所有层级,确保每个深层属性都被访问到。

对象越大、层级越深,成本越高。

所以要避免:

  • 对超大对象无脑 deep: true
  • 明明只关心一个字段,却监听整个对象

8.3 为什么不推荐模板里写复杂表达式

原因有两个:

  • 可读性差
  • 每次渲染都要重新执行

尤其是模板里直接调函数,会在每次重新渲染时执行,不利于性能和维护。

更推荐:

  • computed
  • 把逻辑抽到方法里,但不要在模板高频调用复杂方法

8.4 组件从创建到销毁的大致过程

  1. 创建组件实例
  2. 初始化 props、状态、方法
  3. 建立响应式联系
  4. 编译模板为渲染函数
  5. 挂载并生成真实 DOM
  6. 运行过程中响应式依赖收集
  7. 数据变化触发重新渲染
  8. 组件卸载时清理副作用和资源

8.5 Vue3 为什么用 Proxy

主要是因为 Proxy 的拦截能力更完整。

相比 Object.defineProperty,它更适合处理:

  • 新增属性
  • 删除属性
  • 数组索引
  • Map / Set

同时它是对整个对象做代理,而不是递归劫持每一个属性,设计上更现代。

8.6 composablemixin 的区别

mixin 的问题:

  • 来源不明确
  • 命名容易冲突
  • 多个 mixin 合并后维护困难

composable 的优势:

  • 逻辑来源清楚
  • 可按需组合
  • 参数传递更自然
  • 类型支持更友好

一句话:

Vue3 更推荐用 composable 复用逻辑,而不是继续堆 mixin

8.7 什么叫运行时和编译时协同优化

Vue3 的优化不是只靠运行时。

它会在模板编译阶段提前分析:

  • 哪些内容是静态的
  • 哪些节点是动态的
  • 哪些属性会变化

然后把这些信息交给运行时,运行时就能更精准地更新。


9. 项目开放题答题方向

9.1 项目里遇到过哪些 Vue 性能问题

可以从这些方向答:

  • 大列表渲染卡顿
  • 表单联动过多导致频繁更新
  • 页面初次加载慢
  • 组件复用不当导致重复渲染
  • 大对象深度监听导致卡顿

回答结构建议:

  1. 先说问题现象
  2. 再说定位过程
  3. 最后说解决方案和结果

9.2 封装过哪些组件

可以答:

  • 通用表格组件
  • 表单组件
  • 弹窗组件
  • 上传组件
  • 权限按钮组件
  • 搜索筛选组件
  • 图表组件封装

重点不是“列名字”,而是说清楚:

  • 组件解决了什么问题
  • 输入输出怎么设计
  • 如何兼顾通用性和业务灵活性

9.3 做过哪些工程化建设

常见回答方向:

  • 路由和权限体系
  • 请求封装
  • 统一错误处理
  • 全局状态管理规范
  • 组件库 / 公共组件沉淀
  • ESLint / Prettier / Husky 规范
  • 自动部署和环境配置
  • 性能监控和埋点体系

9.4 从 0 到 1 搭建 Vue 后台项目怎么做

常见回答结构:

  1. 先选技术栈:Vue3 + Vite + Pinia + Vue Router + TS
  2. 规划目录结构和模块边界
  3. 搭建请求层、路由层、权限层
  4. 统一布局和基础组件
  5. 建立状态管理和公共 hooks
  6. 接入规范、构建、环境配置
  7. 最后补监控、测试、发布流程

9.5 面试时怎么回答“你对 Vue 最薄弱的部分是什么”

不要答得太危险。

推荐答法:

  • 可以说“源码细节还在持续深入”
  • 可以说“复杂编译优化和运行时细节还在结合项目理解”
  • 然后补一句“但常规开发、性能优化和组件设计这部分能独立承担”

10. 面试速记版

10.1 高频一句话结论

  • Vue 是一个以响应式系统和组件化为核心的渐进式框架
  • v-if 适合低频切换,v-show 适合高频切换
  • key 的本质是节点身份标识,不能乱用 index
  • computed 解决派生值,watch 解决副作用
  • Vue2 用 Object.defineProperty,Vue3 用 Proxy
  • nextTick 用于拿到更新后的 DOM
  • Vue 更新是异步批处理,为了减少重复渲染
  • Pinia 是 Vue3 时代更推荐的状态管理方案
  • composablemixin 更清晰、更易维护
  • Vue3 的性能提升来自响应式升级和编译优化

10.2 高频易错点

  • 不要把 watchcomputed 混着答
  • 不要说 v-model 就等于双向绑定本身,要补一句“它是语法糖”
  • 不要说前端权限控制就能保证安全,要强调以后端鉴权为准
  • 不要把 nextTick 说成“异步延迟函数”,它的重点是“等待 DOM 更新完成”
  • 不要只说 Vue3 性能更好,要说出原因:Proxy、编译优化、静态提升、Patch Flag

10.3 推荐背诵顺序

  1. Vue 是什么
  2. Vue2 和 Vue3 区别
  3. v-if vs v-show
  4. computed vs watch
  5. 父子组件通信
  6. 生命周期
  7. Vue2 / Vue3 响应式原理
  8. nextTick
  9. diff 和 key
  10. Pinia、权限、组件封装、性能优化