Vue 面试题

#前端#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 2Vue 3
响应式原理Object.definePropertyProxy
API 风格Options APIOptions API + Composition API
源码语言JavaScriptTypeScript
性能一般提升30%-50%
Tree-shaking不支持支持
多根节点不支持支持(Fragment)
生命周期beforeCreate/createdsetup
内置组件无 Teleport/Suspense有 Teleport/Suspense

核心改进

  1. 响应式系统:Vue 3 使用 Proxy 替代 Object.defineProperty,解决了数组索引和对象新增属性监听问题
  2. 编译优化:静态提升(Static Hoisting)+ PatchFlag,减少 Diff 工作量
  3. Tree-shaking:按需引入,减少打包体积
  4. 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

JAVASCRIPT
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'

setup() {
  onBeforeMount(() => {})
  onMounted(() => {})
  // ...
}

4. v-if 和 v-show 的区别?

难度:⭐(基础)
标签:#指令

参考答案要点

特性v-ifv-show
渲染方式条件为 false 时不渲染始终渲染,只是切换 display
切换开销高(创建/销毁组件)低(CSS 切换)
初始渲染可能不渲染始终渲染
适用场景条件很少改变需要频繁切换

注意:v-if 有更高的切换开销,v-show 有更高的初始渲染开销


5. v-for 为什么要配合 key 使用?

难度:⭐⭐(中级)
标签:#列表渲染 #Diff

参考答案要点

key 的作用

  1. 唯一标识:帮助 Vue 识别每个节点的身份
  2. Diff 优化:在虚拟 DOM Diff 过程中,通过 key 快速找到对应节点,复用已有元素
  3. 状态保持:避免就地复用导致的组件状态混乱

不使用 key 的问题

  • Vue 默认使用"就地复用"策略
  • 列表顺序变化时,可能复用错误的元素
  • 表单元素状态会混乱

key 的注意事项

  • 不要使用索引作为 key(除非列表不会变化)
  • 使用唯一 ID 作为 key
  • key 必须是基本类型(string/number)

二、响应式原理

6. Vue 2 的响应式原理是什么?

难度:⭐⭐⭐(高级)
标签:#响应式 #Object.defineProperty

参考答案要点

核心机制:数据劫持 + 发布订阅模式

对象响应式

JAVASCRIPT
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
JAVASCRIPT
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

JAVASCRIPT
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 的区别?

难度:⭐⭐(中级)
标签:#计算属性 #侦听器

参考答案要点

特性computedwatch
用途计算派生数据监听数据变化执行副作用
缓存有缓存,依赖不变不重新计算无缓存
返回值有返回值无返回值
适用场景模板中需要计算的数据数据变化时执行异步/复杂操作
立即执行默认立即执行可配置 immediate
深度监听自动深度依赖收集需配置 deep

computed 使用场景

JAVASCRIPT
const fullName = computed(() => {
  return firstName.value + " " + lastName.value
})

watch 使用场景

JAVASCRIPT
watch(searchText, (newVal, oldVal) => {
  // 执行搜索请求
  fetchSearchResults(newVal)
})

9. Vue 2 如何检测数组变化?有哪些限制?

难度:⭐⭐⭐(高级)
标签:#响应式 #数组

参考答案要点

检测方法

  1. 重写数组方法:Vue 2 重写了数组的 7 个变异方法

    • push/pop/shift/unshift/splice/sort/reverse
  2. 替换数组:使用非变异方法后替换原数组

    JAVASCRIPT
    // 使用 filter/concat/slice 后赋值
    this.items = this.items.filter((item) => item.completed)

限制

  1. 不能检测索引变化

    JAVASCRIPT
    // ❌ 无法检测
    this.items[0] = newValue
    
    // ✅ 解决方案
    this.$set(this.items, 0, newValue)
    // 或
    this.items.splice(0, 1, newValue)
  2. 不能检测长度变化

    JAVASCRIPT
    // ❌ 无法检测
    this.items.length = 0
    
    // ✅ 解决方案
    this.items = []

三、虚拟 DOM 与 Diff 算法

10. 什么是虚拟 DOM?有什么优缺点?

难度:⭐⭐(中级)
标签:#虚拟DOM

参考答案要点

定义:虚拟 DOM 是用 JavaScript 对象描述真实 DOM 结构的树形数据结构。

JAVASCRIPT
// 虚拟 DOM 示例
const vnode = {
  tag: "div",
  props: { id: "app", class: "container" },
  children: [
    { tag: "h1", props: {}, children: ["Hello"] },
    { tag: "p", props: {}, children: ["World"] },
  ],
}

优点

  1. 性能优化:减少直接操作 DOM,批量更新
  2. 跨平台:虚拟 DOM 可以映射到不同平台(Web/Native/小程序)
  3. 开发体验:声明式编程,无需手动操作 DOM

缺点

  1. 内存占用:需要额外的内存存储虚拟 DOM
  2. 首次渲染慢:需要先生成虚拟 DOM
  3. 不适合简单页面:简单页面直接操作 DOM 可能更快

11. Vue 的 Diff 算法原理是什么?

难度:⭐⭐⭐(高级)
标签:#Diff #算法

参考答案要点

核心思想

  1. 同层比较:只比较同一层级的节点,不跨级比较
  2. 双端比较:新旧列表从两端向中间比较
  3. key 优化:通过 key 复用节点

Diff 流程

  1. 比较根节点

    • 类型不同:销毁旧节点,创建新节点
    • 类型相同:复用节点,比较属性
  2. 比较子节点

    • 旧节点无子节点,新节点有:添加子节点
    • 旧节点有子节点,新节点无:删除子节点
    • 都有子节点:执行核心 Diff 算法
  3. 核心 Diff 算法

    • 使用双指针,从新旧列表两端开始比较
    • 找到可复用的节点,移动或更新
    • 处理剩余节点(新增或删除)

Vue 2 vs Vue 3 Diff

特性Vue 2Vue 3
算法双端比较双端比较 + 最长递增子序列
静态节点无优化静态提升,跳过 Diff
PatchFlag有,标记动态内容

12. Vue 3 的静态提升(Static Hoisting)是什么?

难度:⭐⭐⭐(高级)
标签:#编译优化 #性能

参考答案要点

概念:编译阶段将模板中的静态节点提取出来,避免每次渲染重新创建。

示例

VUE
<template>
  <div>
    <h1>静态标题</h1>
    <!-- 静态节点 -->
    <p>{{ dynamic }}</p>
    <!-- 动态节点 -->
  </div>
</template>

编译结果

JAVASCRIPT
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 示例

JAVASCRIPT
// 祖先组件
provide("user", user)

// 后代组件
const user = inject("user")

14. 如何实现自定义指令?

难度:⭐⭐(中级)
标签:#自定义指令

参考答案要点

Vue 2

JAVASCRIPT
Vue.directive("focus", {
  inserted(el) {
    el.focus()
  },
})

Vue 3

JAVASCRIPT
const vFocus = {
  mounted(el) {
    el.focus()
  }
}

// 使用
<input v-focus />

指令钩子

  • created:绑定元素属性前
  • beforeMount:元素挂载前
  • mounted:元素挂载后
  • beforeUpdate:更新前
  • updated:更新后
  • beforeUnmount:卸载前
  • unmounted:卸载后

15. 什么是插槽(Slot)?有哪些类型?

难度:⭐⭐(中级)
标签:#插槽

参考答案要点

插槽类型

  1. 默认插槽

    VUE
    <!-- 子组件 -->
    <slot>默认内容</slot>
    
    <!-- 父组件 -->
    <Child>插入的内容</Child>
  2. 具名插槽

    VUE
    <!-- 子组件 -->
    <slot name="header"></slot>
    <slot name="footer"></slot>
    
    <!-- 父组件 -->
    <Child>
      <template #header>头部</template>
      <template #footer>底部</template>
    </Child>
  3. 作用域插槽

    VUE
    <!-- 子组件 -->
    <slot :user="user"></slot>
    
    <!-- 父组件 -->
    <Child v-slot="{ user }">
      {{ user.name }}
    </Child>

五、Composition API

16. Options API 和 Composition API 的区别?

难度:⭐⭐(中级)
标签:#CompositionAPI #OptionsAPI

参考答案要点

特性Options APIComposition API
代码组织按选项(data/methods/computed)按逻辑功能
逻辑复用Mixins(命名冲突、来源不明)Composable 函数
TypeScript类型推断差类型友好
代码量较少稍多
学习曲线平缓较陡

Composition API 优势

  1. 逻辑复用:通过函数抽取可复用逻辑
  2. 代码组织:相关逻辑放在一起,易于维护
  3. Tree-shaking:更好的代码优化

示例对比

JAVASCRIPT
// 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

参数

JAVASCRIPT
setup(props, context) {
  // props:响应式的 props 对象
  // context:{ attrs, slots, emit, expose }
}

注意点

  1. 无 this:setup 中不能使用 this
  2. 同步执行:setup 必须是同步函数
  3. 返回值:返回的对象可在模板中使用
  4. 响应式:需使用 ref/reactive 创建响应式数据

18. ref 和 reactive 的区别?

难度:⭐⭐(中级)
标签:#响应式 #ref #reactive

参考答案要点

特性refreactive
数据类型任意类型对象/数组
访问方式.value直接访问属性
模板使用自动解包直接访问
替换对象可以替换整个对象不能替换(会失去响应式)

使用建议

  • ref:基础类型、需要替换的对象、组件引用
  • reactive:复杂对象、表单数据

示例

JAVASCRIPT
const count = ref(0)
console.log(count.value) // 0

const state = reactive({ name: "Vue", count: 0 })
console.log(state.name) // Vue

19. 什么是自定义 Hook(Composable)?

难度:⭐⭐⭐(高级)
标签:#Composable #逻辑复用

参考答案要点

定义:将组件逻辑抽取为可复用的函数,以 use 开头命名。

示例

JAVASCRIPT
// 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:离开组件前

执行顺序

  1. 全局 beforeEach
  2. 路由独享 beforeEnter
  3. 组件内 beforeRouteEnter
  4. 全局 beforeResolve
  5. 全局 afterEach

21. hash 模式和 history 模式的区别?

难度:⭐⭐(中级)
标签:#路由模式

参考答案要点

特性hash 模式history 模式
URL 格式/#/path/path
兼容性IE8+IE10+
服务端支持不需要需要(所有路由指向 index.html)
SEO较差较好
实现原理hashchange 事件history.pushState/popstate

history 模式服务端配置

Nginx
# Nginx
location / {
  try_files $uri $uri/ /index.html;
}

七、状态管理

22. Vuex 和 Pinia 的区别?

难度:⭐⭐(中级)
标签:#Vuex #Pinia #状态管理

参考答案要点

特性Vuex 4Pinia
Vue 版本Vue 2/3Vue 2/3
APIOptions APIComposition API
TypeScript类型复杂类型友好
代码量较多(mutations/actions)较少(直接修改 state)
模块需要命名空间自动模块化
体积较大较小(1KB)
开发体验一般更好(DevTools 支持)

Pinia 示例

JAVASCRIPT
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

参考答案要点

核心概念

  1. State:存储应用状态

    JAVASCRIPT
    state: {
      count: 0,
      user: null
    }
  2. Getter:计算属性

    JAVASCRIPT
    getters: {
      doubleCount: (state) => state.count * 2
    }
  3. Mutation:同步修改状态

    JAVASCRIPT
    mutations: {
      increment(state) {
        state.count++
      }
    }
  4. Action:异步操作

    JAVASCRIPT
    actions: {
      async fetchUser({ commit }) {
        const user = await api.getUser()
        commit('setUser', user)
      }
    }
  5. Module:模块化管理

    JAVASCRIPT
    modules: {
      user: userModule,
      cart: cartModule
    }

八、性能优化

24. Vue 项目有哪些性能优化手段?

难度:⭐⭐⭐(高级)
标签:#性能优化

参考答案要点

编码层面

  1. v-show 替代 v-if:频繁切换使用 v-show
  2. v-for 使用 key:避免使用索引作为 key
  3. computed 缓存:复杂计算使用 computed
  4. 函数式组件:无状态组件使用函数式组件
  5. keep-alive:缓存组件状态
  6. 异步组件:路由懒加载

构建层面

  1. 路由懒加载

    JAVASCRIPT
    const Home = () => import("./views/Home.vue")
  2. Tree-shaking:按需引入组件

  3. 代码分割:动态导入

  4. Gzip/Brotli 压缩

运行时层面

  1. 虚拟滚动:大数据列表使用虚拟滚动
  2. 防抖节流:事件处理
  3. SSR/SSG:首屏渲染优化

25. 什么是 keep-alive?有什么作用?

难度:⭐⭐(中级)
标签:#keep-alive #组件缓存

参考答案要点

作用:缓存组件实例,避免重复渲染,保留组件状态。

使用

VUE
<keep-alive :include="['Home', 'User']" :exclude="['Detail']" :max="10">
  <component :is="currentComponent" />
</keep-alive>

属性

  • include:指定缓存的组件名(数组/字符串/正则)
  • exclude:指定不缓存的组件名
  • max:最大缓存实例数(LRU 淘汰)

生命周期

  • activated:组件激活时调用
  • deactivated:组件停用时调用

适用场景

  • 标签页切换
  • 列表页进入详情页后返回保留状态

九、复习建议

重点题目清单

必会题目(⭐⭐⭐ 高频)

  1. Vue 2 和 Vue 3 的区别(响应式原理、API 风格)
  2. 响应式原理(Object.defineProperty vs Proxy)
  3. 虚拟 DOM 和 Diff 算法
  4. computed 和 watch 的区别
  5. 组件间通信方式

重点掌握(⭐⭐ 常考)

  1. 生命周期(Vue 2 vs Vue 3)
  2. v-if 和 v-show 的区别
  3. v-for 的 key 作用
  4. Composition API(setup、ref、reactive)
  5. Vue Router(导航守卫、hash vs history)

加分项(⭐ 了解)

  1. Vue 3 编译优化(静态提升、PatchFlag)
  2. 自定义 Hook
  3. Vuex vs Pinia
  4. 性能优化手段

与 React 对比

面试中经常对比 Vue 和 React,建议准备:

  • 响应式 vs 手动 setState
  • 模板 vs JSX
  • Options API vs Hooks
  • Vuex vs Redux
  • 双向绑定 vs 单向数据流

最后更新于: 2026-02-27