vue前端面试指南


1. Vue 的核心特性是什么?

Vue 的核心特性主要包括:

  • 响应式系统 :通过 Object.defineProperty(Vue 2)或 Proxy(Vue 3)实现数据绑定,数据变化时自动更新视图。
  • 模板语法 :基于 HTML 的声明式模板,支持插值、指令(如 v-ifv-for)等。
  • 组件化 :将 UI 拆分为独立可复用的组件,支持单文件组件(.vue 文件)。
  • 虚拟 DOM:通过虚拟 DOM 比对最小化真实 DOM 操作,提升性能。

2. Vue 2 和 Vue 3 的主要区别?

  • 响应式原理
    Vue 2 基于 Object.defineProperty劫持对象属性的 getter/setter,存在缺陷:无法监听对象新增 / 删除属性、数组索引 / 长度变化,需通过 Vue.set/Vue.delete 解决;
    Vue 3 基于 Proxy 代理整个对象,支持对象新增 / 删除属性、数组索引 / 长度变化,无需额外 API。Vue3 同时支持 reactive(对象 / 数组)和 ref(基础类型),响应式系统更灵活、性能更优。
  • 性能优化:Vue 3 的虚拟 DOM 重写,渲染速度提升;支持 Tree-shaking(按需引入)。
  • Composition API :Vue 3 提供 setup() 函数替代 datamethods 等选项,逻辑组织更灵活。
  • 生命周期beforeDestroydestroyed 更名为 beforeUnmountunmounted
  • 多根节点支持:Vue 3 允许模板包含多个根节点(Fragment)。

3. 什么是 Vue 的生命周期钩子?常用钩子有哪些?

生命周期钩子是 Vue 组件在不同阶段执行的函数。常用钩子:
1、 创建期间的生命周期函数

  • beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性。
  • created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板。
  • beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中。
  • mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示,DOM 已渲染。

2、运行期间的生命周期函数

  • beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点。
  • updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了。

3、销毁期间的生命周期函数

  • beforeUnmount (Vue 3)/ beforeDestroy(Vue 2):实例销毁之前调用。在这一步,实例仍然完全可用。
  • unmounted (Vue 3)/ destroyed(Vue 2):实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

4. Vue 组件间通信有哪些方式?

  • Props 和 Events :父组件通过 props 向子组件传值,子组件通过 $emit 触发事件。
  • $parent / $children:直接访问父/子实例(不推荐,耦合度高)。
  • Provide / Inject :祖先组件通过 provide 提供数据,后代组件通过 inject 注入。
  • Event Bus :全局事件总线(Vue 2 常用,Vue 3 推荐用 mitt 等库)。
  • Vuex / Pinia:状态管理库,集中管理跨组件数据。
  • $refs:通过模板引用直接访问子组件方法或数据。

5. Vue 的响应式原理是什么?(Vue 3)

一、 响应式核心思想

Vue 响应式的本质是:通过拦截数据的读取与修改操作,建立 "数据依赖" 与 "更新逻辑" 的映射关系,当数据发生变化时,自动触发对应的更新逻辑(如视图渲染、回调执行等)。

简单拆解为 3 个核心步骤:

  1. 依赖收集:当数据被读取时(如模板渲染、计算属性依赖、侦听器监听),记录下 "谁在使用这个数据"(即依赖);
  2. 数据拦截:对数据的修改操作进行拦截,感知数据的变化;
  3. 派发更新:当数据发生变化时,通知所有之前收集的依赖,执行对应的更新逻辑(如重新渲染视图、执行 watch 回调)。

二、 Vue 2.x 响应式原理(Object.defineProperty 实现)

Vue 2.x 基于 Object.defineProperty API 实现响应式,仅支持对对象属性和数组方法的拦截,不支持原生 Map/Set 等数据结构。

  1. 核心 API:Object.defineProperty
    Object.defineProperty 是 ES5 提供的 API,用于给对象定义或修改属性,并可以拦截该属性的 get(读取)和 set(修改)操作,这是 Vue 2.x 响应式的基础。

基础用法示例(模拟响应式拦截)

javascript 复制代码
// 原始数据
const data = {
  name: '张三',
  age: 20
}

// 遍历对象,为每个属性添加响应式拦截
Object.keys(data).forEach(key => {
  // 保存属性原始值
  let value = data[key]

  // 定义属性,拦截 get 和 set
  Object.defineProperty(data, key, {
    // 可枚举(遍历对象时能获取到该属性)
    enumerable: true,
    // 可配置(后续可修改属性配置)
    configurable: true,
    // get 方法:属性被读取时触发(依赖收集的时机)
    get() {
      console.log(`读取 ${key} 属性:${value}`)
      // 依赖收集:此处会记录"谁读取了这个属性"
      // Dep.target.addDep(this) // 简化示意,Vue 内部通过 Dep 类管理依赖
      return value
    },
    // set 方法:属性被修改时触发(派发更新的时机)
    set(newValue) {
      if (newValue === value) return // 新值与旧值一致,不触发更新
      console.log(`修改 ${key} 属性:${value} → ${newValue}`)
      value = newValue // 更新原始值
      // 派发更新:通知所有依赖该属性的对象执行更新逻辑
      // dep.notify() // 简化示意,Vue 内部通过 Dep 类派发更新
    }
  })
})

// 测试:读取属性(触发 get)
console.log(data.name) // 输出:读取 name 属性:张三 → 张三

// 测试:修改属性(触发 set)
data.age = 21 // 输出:修改 age 属性:20 → 21
  1. Vue 2.x 响应式核心模块

    Vue 2.x 内部通过 3 个核心类配合 Object.defineProperty 实现完整响应式:

    类名 作用

    Observer 递归遍历对象的所有属性,为每个属性添加 get/set 拦截,将普通对象转为响应式对象;对数组则重写其 7 个变异方法(push/pop/shift/unshift/splice/sort/reverse)。

    Dep(Dependency) 依赖管理器,每个响应式属性对应一个 Dep 实例,用于收集(addDep)和存储该属性的所有依赖(Watcher 实例),并在数据变化时派发更新(notify)。

    Watcher 依赖的载体(也叫观察者),分为渲染 Watcher(对应模板视图)、计算属性 Watcher、侦听器 Watcher 三种。当数据变化时,Dep 会通知 Watcher 执行 update 方法,触发对应的更新逻辑(如重新渲染视图、执行 watch 回调)。

  2. Vue 2.x 响应式执行流程(完整链路)

    初始化阶段:new Vue() 后,Vue 会调用 Observer 对 data 中的对象进行递归遍历,为每个属性添加 get/set 拦截,同时为每个属性创建 Dep 实例;

    模板渲染阶段:解析模板时,会创建渲染 Watcher,并将 Dep.target 指向该 Watcher;当读取 data 中的属性时(如 {{ name }}),触发 get 方法,Dep 会将当前 Watcher 添加到依赖列表中(依赖收集);

    数据修改阶段:当通过 this.name = '李四' 修改属性时,触发 set 方法,对应的 Dep 实例会调用 notify 方法,遍历所有收集的 Watcher,执行 update 方法;

    更新视图阶段:Watcher 的 update 方法会触发模板重新解析和渲染,最终实现视图与数据的同步。

  3. Vue 2.x 响应式的局限性

    无法监听对象新增 / 删除属性:Object.defineProperty 只能拦截已存在的属性,新增属性(this.data.newKey = 'xxx')或删除属性(delete this.data.key)不会触发视图更新,需要通过 Vue.set/this.set、Vue.delete/this.set、Vue.delete/this.set、Vue.delete/this.delete 手动触发;

    无法监听数组索引和长度变化:由于性能原因,Vue 2.x 没有对数组索引和长度的修改进行拦截(如 arr[0] = 10、arr.length = 0),只能通过重写的 7 个变异方法触发更新;

    对复杂数据结构支持有限:不支持原生 Map、Set、WeakMap 等数据结构的响应式监听;

    递归拦截性能损耗:Observer 会递归遍历对象的所有属性,当对象层级过深、属性过多时,会影响初始化性能。

三、 Vue 3.x 响应式原理(Proxy 实现)

Vue 3.x 放弃了 Object.defineProperty,转而使用 ES6 提供的 Proxy API 实现响应式,同时配合 Reflect API 进行属性操作,解决了 Vue 2.x 的所有局限性,且性能更优。

  1. 核心 API:Proxy + Reflect
    (1) Proxy 特性
    Proxy 用于创建一个对象的 "代理",可以拦截该对象的所有属性操作(包括读取、修改、新增、删除、遍历等),相比 Object.defineProperty 具有更强的拦截能力,且直接作用于整个对象,而非单个属性。
    (2) Reflect 特性
    Reflect 同样是 ES6 提供的 API,用于执行对象的原生操作,它的方法与 Proxy 的拦截方法一一对应。Vue 3.x 中使用 Reflect 的目的:
    确保属性操作的行为与原生一致;
    可以获取属性操作的返回值(如 Reflect.set 会返回布尔值,表示设置是否成功);
    解决 this 指向问题,避免代理对象操作时的上下文丢失。

基础用法示例(模拟 Vue 3.x 响应式)

javascript 复制代码
// 原始数据
const data = {
  name: '张三',
  age: 20,
  hobbies: ['篮球', '读书']
}

// 创建 Proxy 代理,拦截整个对象
const reactiveData = new Proxy(data, {
  // get 方法:拦截属性读取(包括对象属性、数组索引、原型属性等)
  get(target, key, receiver) {
    console.log(`读取 ${key} 属性:${target[key]}`)
    // 依赖收集:此处记录依赖关系
    // track(target, key) // Vue 3 内部通过 track 函数收集依赖
    // 使用 Reflect 读取属性,确保行为原生
    return Reflect.get(target, key, receiver)
  },

  // set 方法:拦截属性修改、新增(包括对象属性、数组索引等)
  set(target, key, newValue, receiver) {
    console.log(`修改/新增 ${key} 属性:${target[key]} → ${newValue}`)
    // 派发更新:通知依赖执行更新逻辑
    // trigger(target, key) // Vue 3 内部通过 trigger 函数派发更新
    // 使用 Reflect 设置属性,返回操作结果
    return Reflect.set(target, key, newValue, receiver)
  },

  // deleteProperty 方法:拦截属性删除
  deleteProperty(target, key) {
    console.log(`删除 ${key} 属性`)
    // 派发更新
    // trigger(target, key)
    // 使用 Reflect 删除属性,返回操作结果
    return Reflect.deleteProperty(target, key)
  }
})

// 测试 1:读取属性(触发 get)
console.log(reactiveData.name) // 输出:读取 name 属性:张三 → 张三

// 测试 2:修改属性(触发 set)
reactiveData.age = 21 // 输出:修改/新增 age 属性:20 → 21

// 测试 3:新增属性(触发 set,Vue 2.x 不支持此特性)
reactiveData.gender = '男' // 输出:修改/新增 gender 属性:undefined → 男

// 测试 4:删除属性(触发 deleteProperty,Vue 2.x 不支持此特性)
delete reactiveData.age // 输出:删除 age 属性

// 测试 5:修改数组索引(触发 set,Vue 2.x 不支持此特性)
reactiveData.hobbies[0] = '足球' // 输出:修改/新增 0 属性:篮球 → 足球
  1. Vue 3.x 响应式核心函数
    Vue 3.x 采用函数式设计,取消了 Vue 2.x 的 Observer、Dep 类,通过以下核心函数实现响应式:
    函数 作用
    reactive 接收一个普通对象 / 数组,返回其 Proxy 代理对象,实现对象类型数据的响应式(深层响应式);支持新增、删除属性,支持数组索引 / 长度修改。
    ref 接收基本数据类型(string/number/boolean 等)或对象,返回一个带有 .value 属性的 Ref 实例,解决基本数据类型无法被 Proxy 拦截的问题;访问 / 修改其值时需通过 .value(模板中可省略)。
    track 依赖收集函数,在 Proxy 的 get 拦截中调用,记录 "目标对象 - 属性 - 依赖" 的映射关系,依赖存储在 WeakMap 结构中(更节省内存)。
    trigger 派发更新函数,在 Proxy 的 set/deleteProperty 拦截中调用,根据 "目标对象 - 属性" 找到所有依赖,执行对应的更新逻辑。
    effect 副作用函数,包裹需要响应式执行的逻辑(如模板渲染、计算属性逻辑、watch 回调);当依赖的数据变化时,副作用函数会自动重新执行。
  2. Vue 3.x 响应式的优势

更强的拦截能力: 支持对象新增 / 删除属性、数组索引 / 长度修改,无需手动调用 set/set/set/delete;
支持更多数据类型: 原生支持 Map、Set、WeakMap、WeakSet 等复杂数据结构的响应式监听;
性能更优:

  • 非递归初始化:Proxy 直接作用于整个对象,初始化时无需递归遍历所有属性,仅在属性被读取时才进行深层代理(懒代理),提升初始化性能;
  • 内存更高效:使用 WeakMap 存储依赖关系,依赖对象被销毁时会自动释放内存,避免内存泄漏;

支持基本数据类型: 通过 ref 函数实现基本数据类型的响应式,弥补了 Proxy 无法直接拦截基本数据类型的缺陷;
更好的兼容性: 配合 Reflect API,解决了 Object.defineProperty 的诸多兼容性问题,且更符合 ES 规范。

四、 响应式原理关键总结

  1. 核心本质:拦截数据操作(读取 / 修改 / 新增 / 删除),建立 "数据 - 依赖"

    映射,数据变化自动触发依赖更新,实现视图与数据同步;

  2. 版本差异:Vue 2.x 基于 Object.defineProperty(单个属性拦截,有局限性),Vue 3.x 基于 Proxy

    • Reflect(整个对象拦截,功能更强、性能更优);
  3. 核心步骤:依赖收集(读取数据时)→ 数据拦截(监听数据变化)→ 派发更新(通知依赖执行更新逻辑);

  4. 补充点:Vue 3.x 中 reactive 用于对象类型,ref 用于基本类型,二者共同覆盖所有数据类型的响应式需求;

  5. 核心优势:无需手动操作 DOM,简化开发流程,提升开发效率,同时保证视图与数据的一致性。


6. 什么是 Computed 和 Watch?区别是什么?

Computed(计算属性)

1. 核心概念

Computed 即计算属性,它是基于依赖的响应式数据进行缓存计算的属性。简单来说,它是一个 "派生值"------ 通过已有数据(Vue 实例的 data/props/ 其他计算属性)经过逻辑处理后得到的新值,本质是一个带有缓存的 getter 函数(可配置 setter 成为读写属性)。

2. 核心特性

  • (1) 缓存机制(最核心特性)
    计算属性会缓存计算结果,只有当它的依赖数据发生变化时,才会重新执行计算逻辑,返回新的结果;若依赖数据未改变,多次访问计算属性会直接返回缓存的旧结果,不会重复计算。
    示例理解:若 fullName 依赖 firstName 和 lastName,只要 firstName 和 lastName 不变,无论多少次访问 fullName,都不会重新拼接字符串,直接返回缓存值。
  • (2) 声明式编程
    Computed 采用 "声明式" 写法,我们只需要声明 "计算结果依赖什么数据""如何得到结果",无需关心数据变化的时机,Vue 会自动追踪依赖并完成更新。
  • (3) 默认只读(可配置为读写)
    计算属性默认只有 getter 方法,即仅支持获取值,不支持直接修改;若需要修改计算属性,可手动配置 setter 方法。
  • (4) 必须返回一个值
    计算属性的核心是 "计算出一个结果",因此 getter 函数必须有返回值,这个返回值就是计算属性的最终值。

Watch(侦听器)

1. 核心概念

Watch 即侦听器(也叫监听器),它的核心作用是主动监听一个或多个响应式数据的变化,当数据发生改变时,执行指定的回调函数(副作用函数),用于处理复杂的业务逻辑(如异步操作、多步骤数据处理等)。

2. 核心特性

  • (1) 无缓存机制
    Watch 没有缓存,只要被监听的数据发生变化,就会立即执行回调函数,无论回调函数的逻辑是否产生相同结果,都会重复执行。
  • (2) 命令式编程
    Watch 采用 "命令式" 写法,我们需要明确指定 "监听哪个数据""数据变化后执行什么逻辑",主动处理数据变化的后续操作。
  • (3) 支持异步操作
    这是 Watch 与 Computed 的关键区别之一,Watch 的回调函数中可以自由执行异步操作(如接口请求、定时器等),而 Computed 的 getter 函数不支持异步(异步操作会导致无法及时返回计算结果,破坏缓存机制)。
  • (4) 可监听单个 / 多个数据
    Watch 既可以监听单个响应式数据,也可以监听多个响应式数据,还能深度监听对象 / 数组的内部属性变化。
  • (5) 无强制返回值要求
    Watch 的回调函数是用于执行 "副作用"(如修改其他数据、发起请求、操作 DOM 等),无需返回任何值。

7. Vuex 的核心概念有哪些?

答案:

  • State:存储应用状态的单一数据源。
  • Mutations :同步修改 State 的方法(通过 commit 触发)。
  • Actions :处理异步操作,通过 dispatch 触发,可调用 Mutations。
  • Getters:计算派生状态(类似 Computed)。
  • Modules:将 Store 分割为模块,便于大型项目管理。

8. Vue Router 的路由守卫有哪些?

路由守卫用于控制导航流程:

  • 全局守卫beforeEachbeforeResolveafterEach
  • 路由独享守卫 :在路由配置中定义的 beforeEnter
  • 组件内守卫beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave

Vue Router 支持两种路由模式,核心区别在于 URL 格式和底层实现原理,分别适配不同场景。

  1. history 模式(推荐,优雅 URL)

(1) 实现原理

基于 HTML5 History API(pushState、replaceState、popstate 事件),通过操作浏览器的历史记录栈,修改 URL 路径而不发起 HTTP 请求。
pushState: 添加一条新的历史记录,修改 URL 路径(对应 router.push);
replaceState: 替换当前历史记录,修改 URL 路径(对应 router.replace);
popstate: 监听浏览器前进 / 后退按钮触发的历史记录变化,Vue Router 内部通过该事件实现视图切换。

(2) URL 格式

无 # 号,与传统网站 URL 一致,更优雅美观:

plaintext

https://xxx.com/home

https://xxx.com/user/1001

https://xxx.com/article?category=vue

(3) 注意事项

依赖后端配置:因为 SPA 只有一个 HTML 文件,当用户直接访问非根路径(如 https://xxx.com/user/1001)时,后端会默认查找该路径对应的文件,导致 404 错误;

后端解决方案:配置所有路由请求都重定向到 SPA 的入口 HTML 文件(如 index.html),Nginx/Apache/NestJS 等均有对应配置方案。

  1. hash 模式(兼容模式,无需后端配置)

(1) 实现原理

基于 URL 的 hash 部分(# 后面的内容,如 https://xxx.com/#/home),hash 变化不会触发浏览器的页面刷新,也不会发起 HTTP 请求,Vue Router 通过监听 hashchange 事件感知 hash 变化,进而切换视图。

(2) URL 格式

带有 # 号,兼容性更好(支持 IE 10 及以下),无需后端配置:

plaintext

https://xxx.com/#/home

https://xxx.com/#/user/1001

https://xxx.com/#/article?category=vue

(3) 优缺点

优点:兼容性强、无需后端配置、使用简单;

缺点:URL 带有 # 号,不够优雅;部分第三方服务(如微信支付)可能会忽略 hash 部分,导致参数传递异常。


9. 如何优化 Vue 应用性能?

  • 虚拟 DOM 优化 :减少不必要的渲染(如用 v-show 替代 v-if 频繁切换的组件)。
  • 使用 Computed 缓存:避免重复计算。
  • 异步组件 :通过 defineAsyncComponent() => import() 实现按需加载。
  • 列表性能 :为 v-for 添加 key,避免使用 v-ifv-for 同时作用。
  • 事件销毁 :在 beforeUnmount 中移除全局事件监听(如 window.addEventListener)。

10. Vue 3 的 Composition API 解决了什么问题?

  • 逻辑复用 :通过自定义 Hook(如 useFetch)替代 Mixins,避免命名冲突。
  • 代码组织 :将同一功能的代码(如数据、方法)聚合在 setup() 中,提高可读性。
  • TypeScript 支持:更好的类型推断,减少类型声明冗余。

附:Vue 3 组合式 API 示例

javascript 复制代码
import { ref, computed, onMounted } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const double = computed(() => count.value * 2);

    const increment = () => count.value++;

    onMounted(() => {
      console.log('组件已挂载');
    });

    return { count, double, increment };
  }
};

以上问题覆盖了 Vue 的核心知识点,建议结合实践加深理解!

相关推荐
名字被你们想完了2 小时前
Flutter 实现一个容器内部元素可平移、缩放和旋转等功能(十)
前端·flutter
千寻girling2 小时前
面试官: “ 请你讲一下 package.json 文件 ? ”
前端·javascript·面试
如果你好2 小时前
解决深拷贝循环引用痛点:一篇看懂 WeakMap 实现方案
前端·javascript
han_2 小时前
前端性能优化之性能指标篇
前端·javascript·性能优化
小p2 小时前
nextjs学习1:回顾服务端渲染SSR
vue.js
Java天梯之路2 小时前
# Spring Boot 钩子全集实战(四):`SpringApplicationRunListener.environmentPrepared()` 详解
java·spring·面试
大布布将军2 小时前
⚡部署的通行证:Docker 容器化基础
运维·前端·学习·程序人生·docker·容器·node.js
Irene19912 小时前
Vue:defineProps、defineEmits、defineExpose 深度解析
vue.js·编译器宏