vue3面试题

Vue 3 的面试题主要围绕组合式 API (Composition API)响应式原理性能优化 以及与 Vue 2 的区别展开。以下是高频核心考点及标准答案:

一、 核心原理类(必考)

1. Vue 3 的响应式原理是什么?与 Vue 2 有何区别?

核心答案

  • Vue 3 :使用 Proxy 实现响应式。通过创建对象的代理,拦截对象的读取(get)和设置(set)操作,实现依赖收集和派发更新。
  • Vue 2 :使用 Object.defineProperty 递归遍历对象属性,通过 getter/setter 劫持数据。

区别与优势

  • 性能:Proxy 是懒代理,无需递归初始化所有属性,性能更好。
  • 功能 :Proxy 能监听新增/删除属性数组索引/长度变化 ,无需 Vue.set/Vue.delete
  • 支持类型:Proxy 支持 Map、Set 等复杂数据结构,Vue 2 不支持。
2. Vue 3 为什么这么快?(性能优化)

核心答案

  • 响应式重构:Proxy 替代 defineProperty,减少初始化开销。
  • 编译时优化
    • 静态提升 (Hoist Static):将静态节点提升到渲染函数外,避免重复创建。
    • Patch Flag:给动态节点打标记,Diff 时只对比带标记的节点,跳过静态节点。
    • Tree Shaking:支持按需引入,未使用的 API 不会被打包。

二、 组合式 API (Composition API)

3. 组合式 API 与选项式 API 的区别?

核心答案

  • 代码组织 :组合式 API 按逻辑功能 组织代码(如将用户逻辑放在一起),选项式 API 按选项组织(data、methods 分离)。
  • 逻辑复用 :组合式 API 通过自定义 Hook (Composables) 复用逻辑,解决了 Mixins 的命名冲突和来源不清晰问题。
  • TypeScript 支持:组合式 API 天然支持更好的类型推断。
4. refreactive 的区别?

核心答案

  • ref :用于包装基本类型 (String, Number),通过 .value 访问。也可包装对象,内部调用 reactive
  • reactive :用于包装对象/数组,返回代理对象,直接访问属性。
  • 选择原则 :基本类型用 ref,对象/数组用 reactive
5. watchwatchEffect 的区别?

核心答案

  • watch:需要明确指定监听的数据源,支持获取旧值和新值,适合精确监听。
  • watchEffect :自动收集回调函数内的依赖,立即执行一次,适合依赖动态变化的场景。

三、 生命周期与新特性

6. Vue 3 生命周期有哪些变化?

核心答案

  • 重命名beforeDestroybeforeUnmountdestroyedunmounted
  • 组合式写法 :使用 onMountedonUnmounted 等函数。
  • setup 替代setup 函数执行时机相当于 Vue 2 的 beforeCreatecreated,这两个钩子在 Vue 3 中不再推荐使用。
7. Vue 3 有哪些新特性?

核心答案

  • Fragment:支持多根节点模板。
  • Teleport:将组件渲染到 DOM 的指定位置(如 body 下的弹窗)。
  • Suspense:处理异步组件的加载状态。
  • <script setup>:编译时语法糖,简化组合式 API 的写法。

四、 实战与场景题

8. 如何实现逻辑复用?

核心答案

使用组合式函数 (Composables) 。将逻辑封装为函数,返回响应式数据和方法,在组件中引入使用。例如封装 useCounter 管理计数逻辑。

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

核心答案

  • 编译层:利用静态提升和 Patch Flag。
  • 代码层 :使用 shallowRef/shallowReactive 避免深层响应式代理;使用 v-memo 缓存模板片段。
  • 工程层 :组件懒加载(defineAsyncComponent),路由懒加载。

五、 加分项(进阶)

  • Diff 算法优化:Vue 3 引入 Block Tree 和 Patch Flag,只追踪动态节点,减少 Diff 范围。
  • Tree Shaking 原理:Vue 3 源码采用 ES Module 模块化,打包工具(如 Webpack)能识别未使用的导出并剔除。

Vue 3 的组件传值方式多样,主要分为父子通信跨级/多级通信全局状态三大类。以下是所有方式的总结与对比:

一、 父子组件通信(最常用)

1. Props / Emits(单向数据流)
  • 父传子 (Props)

    vue 复制代码
    // 父组件
    <template>
      <Child :title="msg" :count="10" />
    </template>
    <script setup>
    import { ref } from 'vue';
    const msg = ref('Hello');
    </script>
    vue 复制代码
    // 子组件
    <script setup>
    // 接收 Props
    const props = defineProps({
      title: String,
      count: { type: Number, default: 0 }
    });
    </script>
  • 子传父 (Emits)

    vue 复制代码
    // 子组件
    <template>
      <button @click="handleClick">传值</button>
    </template>
    <script setup>
    const emit = defineEmits(['update']);
    const handleClick = () => {
      emit('update', { data: '子组件的数据' });
    };
    </script>
    vue 复制代码
    // 父组件
    <template>
      <Child @update="handleUpdate" />
    </template>
    <script setup>
    const handleUpdate = (value) => {
      console.log(value); // { data: '子组件的数据' }
    };
    </script>
2. v-model 双向绑定(语法糖,用于表单组件)
  • 父组件

    vue 复制代码
    <template>
      <Child v-model="name" />
    </template>
    <script setup>
    const name = ref('');
    </script>
  • 子组件

    vue 复制代码
    <template>
      <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
    </template>
    <script setup>
    const props = defineProps(['modelValue']);
    const emit = defineEmits(['update:modelValue']);
    </script>
3. ref 和 defineExpose(获取子组件实例)
  • 父组件

    vue 复制代码
    <template>
      <Child ref="childRef" />
    </template>
    <script setup>
    import { ref, onMounted } from 'vue';
    const childRef = ref(null);
    onMounted(() => {
      // 调用子组件暴露的方法
      childRef.value.someMethod();
    });
    </script>
  • 子组件

    vue 复制代码
    <script setup>
    const someMethod = () => { console.log('子组件方法'); };
    // 暴露给父组件
    defineExpose({ someMethod });
    </script>

二、 跨级/兄弟组件通信

4. Provide / Inject(依赖注入,解决多级嵌套传值)
  • 祖先组件 (Provide)

    vue 复制代码
    <script setup>
    import { provide, ref } from 'vue';
    const count = ref(0);
    // 提供响应式数据
    provide('countKey', count);
    </script>
  • 后代组件 (Inject)

    vue 复制代码
    <script setup>
    import { inject } from 'vue';
    const count = inject('countKey');
    </script>
5. 事件总线 (Event Bus)(已不推荐)

Vue 3 取消了 $on$off 等 API,建议使用第三方库(如 mitt)替代:

javascript 复制代码
import mitt from 'mitt';
const emitter = mitt();
// 发送
emitter.emit('update', data);
// 接收
emitter.on('update', (data) => { console.log(data); });

三、 全局状态管理

6. Pinia(官方推荐)
  • 定义 Store (store/user.js)

    javascript 复制代码
    import { defineStore } from 'pinia';
    export const useUserStore = defineStore('user', {
      state: () => ({ name: '张三' }),
      actions: {
        updateName(newName) { this.name = newName; }
      }
    });
  • 组件中使用

    vue 复制代码
    <template>
      <div>{{ userStore.name }}</div>
    </template>
    <script setup>
    import { useUserStore } from '@/stores/user';
    const userStore = useUserStore();
    // 修改状态
    userStore.updateName('李四');
    </script>
7. Vuex 4(Vue 3 兼容版)
  • 仍可使用,但官方已推荐迁移至 Pinia。

四、 总结与选择建议

场景 推荐方式 说明
父子组件 Props / Emits, v-model 简单、清晰,符合单向数据流
获取子组件实例 ref + defineExpose 父组件需要调用子组件方法时使用
多级嵌套 Provide / Inject 避免逐层传递,适合主题、配置等
兄弟组件 / 无关联组件 Pinia 全局状态管理,替代 Vuex
简单事件通信 mitt 等事件库 小型项目快速实现事件通信

最佳实践

  • 优先使用 Props / Emits 处理父子通信。
  • 复杂应用的状态管理首选 Pinia
  • 主题、用户信息等全局数据使用 Provide / InjectPinia

Vue 3 的路由守卫与 Vue 2 类似,但需在 Vue Router 4 中使用。路由守卫主要用于控制导航权限,分为全局守卫路由独享守卫组件内守卫三类。

一、 全局守卫(在路由实例上定义)

1. 全局前置守卫 beforeEach(最常用)
  • 执行时机 :路由切换,常用于登录验证、权限检查。

  • 使用示例

    javascript 复制代码
    import { createRouter, createWebHistory } from 'vue-router';
    
    const router = createRouter({ /* ... */ });
    
    router.beforeEach((to, from, next) => {
      // 1. 检查是否需要登录
      if (to.meta.requiresAuth && !isLoggedIn()) {
        // 未登录,跳转到登录页
        next({ path: '/login', query: { redirect: to.fullPath } });
      } else if (to.path === '/login' && isLoggedIn()) {
        // 已登录却访问登录页,重定向到首页
        next({ path: '/' });
      } else {
        // 放行
        next();
      }
    });
2. 全局解析守卫 beforeResolve
  • 执行时机 :在导航被确认所有组件内守卫和异步路由组件被解析之后。适合处理需要确保数据加载完成的场景。
3. 全局后置守卫 afterEach
  • 执行时机 :路由切换,常用于日志记录、页面标题设置。

  • next 函数

    javascript 复制代码
    router.afterEach((to, from) => {
      // 设置页面标题
      document.title = to.meta.title || '默认标题';
      // 发送页面访问统计
      logAnalytics(to.fullPath);
    });

二、 路由独享守卫(在路由配置中定义)

在单个路由的配置对象中使用 beforeEnter,只对该路由生效。

javascript 复制代码
const routes = [
  {
    path: '/dashboard',
    component: Dashboard,
    meta: { requiresAuth: true },
    beforeEnter: (to, from, next) => {
      // 检查用户权限
      if (!hasPermission('admin')) {
        next({ path: '/403' }); // 无权限,跳转到403页
      } else {
        next(); // 放行
      }
    }
  }
];

三、 组件内守卫(在组件中定义)

1. beforeRouteEnter
  • 执行时机 :进入组件 ,此时组件实例尚未创建

  • 特殊用法 :可以通过 next(vm => {}) 访问组件实例。

    javascript 复制代码
    <script setup>
    import { onBeforeRouteEnter } from 'vue-router';
    
    onBeforeRouteEnter((to, from, next) => {
      // 无法访问 this
      next(vm => {
        // 通过 vm 访问组件实例
        console.log(vm.someData);
      });
    });
    </script>
2. beforeRouteUpdate
  • 执行时机 :当前路由改变,但组件被复用时 (如从 /user/1 跳转到 /user/2)。

  • 用法

    javascript 复制代码
    <script setup>
    import { onBeforeRouteUpdate } from 'vue-router';
    
    onBeforeRouteUpdate((to, from, next) => {
      // 重新获取数据
      fetchUserData(to.params.id);
      next();
    });
    </script>
3. beforeRouteLeave
  • 执行时机 :离开当前组件对应路由,常用于防止未保存离开。

  • 用法

    javascript 复制代码
    <script setup>
    import { onBeforeRouteLeave } from 'vue-router';
    
    onBeforeRouteLeave((to, from, next) => {
      if (formHasUnsavedChanges.value) {
        const confirmLeave = window.confirm('有未保存的更改,确定离开吗?');
        if (confirmLeave) {
          next(); // 确认离开
        } else {
          next(false); // 取消导航
        }
      } else {
        next(); // 直接离开
      }
    });
    </script>

四、 组合式 API 写法

<script setup> 中,需从 vue-router 导入对应的组合式函数:

vue 复制代码
<script setup>
import { onBeforeRouteEnter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router';

// 组件内守卫
onBeforeRouteEnter((to, from, next) => { /* ... */ });
onBeforeRouteUpdate((to, from, next) => { /* ... */ });
onBeforeRouteLeave((to, from, next) => { /* ... */ });
</script>

五、 执行顺序总结

完整的导航解析流程如下:

  1. 触发导航 :调用 router.push() 或点击 <router-link>
  2. 调用失活组件的 beforeRouteLeave
  3. 调用全局的 beforeEach
  4. 调用重用组件的 beforeRouteUpdate(如果组件被复用)。
  5. 调用路由配置中的 beforeEnter
  6. 解析异步路由组件
  7. 调用被激活组件的 beforeRouteEnter
  8. 调用全局的 beforeResolve
  9. 导航被确认
  10. 调用全局的 afterEach
  11. 触发 DOM 更新

六、 最佳实践

  • 权限校验 :在 beforeEach 中统一处理登录状态和路由权限。
  • 数据预取 :在 beforeRouteEnterbeforeResolve 中获取必要数据。
  • 离开确认 :在 beforeRouteLeave 中提示用户保存未提交的数据。
  • 组合式 API 优先 :新项目使用组合式 API 的守卫函数,与 <script setup> 风格保持一致。

以下是 Vue 3 的核心原理面试题,涵盖了响应式、虚拟DOM、编译优化、生命周期等多个维度的高频考点:

一、 响应式原理

1. Vue 3 的响应式原理是什么?(与 Vue 2 对比)
  • 核心机制 :Vue 3 使用 ES6 的 Proxy 代理对象,拦截对象的读取和写入操作。依赖收集和触发更新在拦截器中自动完成。

  • 对比 Vue 2

    Vue 2 Vue 3
    使用 Object.defineProperty 劫持 使用 Proxy 代理
    递归遍历所有属性,初始化性能差 惰性代理,访问时递归
    无法检测新增/删除属性 可直接检测
    无法原生支持 Map/Set 原生支持
    数组需重写 7 个方法 可直接检测索引变化
  • 代码示例原理

    javascript 复制代码
    // 简化版原理
    function reactive(obj) {
      return new Proxy(obj, {
        get(target, key, receiver) {
          track(target, key); // 收集依赖
          return Reflect.get(target, key, receiver);
        },
        set(target, key, value, receiver) {
          const result = Reflect.set(target, key, value, receiver);
          trigger(target, key); // 触发更新
          return result;
        }
      });
    }
2. ref 和 reactive 的实现原理区别?
  • ref :内部用 class RefImpl 包装。如果是基本类型,通过 .value 的 getter/setter 劫持(类似 Vue 2);如果是对象,内部转为 reactive 代理。
  • reactive:直接使用 Proxy 代理整个对象。
3. 依赖收集(track)和派发更新(trigger)如何工作?
  • 依赖收集 :在 effect(副作用函数,如组件的 render 函数)执行时,触发 get 拦截,将当前 effect 存入一个"桶"(target -> key -> effect 的 Map 结构)。
  • 派发更新 :当数据变化触发 set 时,从"桶"中取出对应的 effect 重新执行。

二、 虚拟 DOM 与 Diff 算法

4. Vue 3 的虚拟 DOM 有什么优化?
  • 静态提升 (Static Hoisting):编译时将静态节点提取到渲染函数外部,避免重复创建。
  • Patch Flag :为动态节点添加标记(如 1 表示文本动态),Diff 时只对比带标记的节点。
  • 区块树 (Block Tree):将模板分为静态区和动态区,减少递归遍历深度。
5. Diff 算法的优化点?

Vue 3 引入 最长递增子序列 (LIS) 算法优化对比效率:

  • 同层级对比:与 Vue 2 一致,只对比同级节点。
  • Key 的重要性:Key 是复用节点的唯一标识,避免就地复用导致的状态错误。
  • 优化流程
    1. 预处理:跳过前置和后置的相同节点。
    2. 如果新旧节点顺序变化,通过 LIS 找到最长稳定序列,最小化移动次数。

三、 编译优化

6. Tree Shaking 是如何实现的?

Vue 3 源码采用 ES Module 模块化。打包工具(如 Webpack、Vite)在打包时会分析导入导出,移除未被使用的模块。例如,如果不使用 transition 组件,则最终打包产物不会包含相关代码。

7. 静态节点提升 (Static Hoisting) 如何提升性能?
  • 编译前:模板中的静态节点在每次渲染时都会重新创建 VNode。
  • 编译后:静态节点被提取为常量,在渲染函数外创建一次,后续渲染直接复用。

四、 组合式 API 原理

8. 为什么组合式 API 解决了 Mixins 的问题?
  • 命名冲突:Mixins 合并时同名属性/方法会被覆盖,组合式 API 通过函数作用域隔离。
  • 来源不清晰:Mixins 的属性和方法来源不明确,组合式 API 的变量显示导入。
  • 逻辑复用:组合式函数可按功能划分,更灵活。
9. <script setup> 编译时做了什么?
  • 将模板编译为 render 函数。
  • 自动将顶层变量、函数暴露给模板,无需 return
  • 自动处理 definePropsdefineEmits 等编译宏。

五、 生命周期与渲染流程

10. Vue 3 的生命周期有哪些变化?
  • 重命名:beforeDestroybeforeUnmountdestroyedunmounted
  • 新增:renderTracked(调试响应式依赖收集)、renderTriggered(调试响应式触发更新)。
  • setup 替代了 beforeCreatecreated
11. 响应式数据变化到视图更新的全过程?
  1. 数据变更 :修改响应式数据,触发 set 拦截。
  2. 触发 effect :从依赖"桶"中找到关联的 effect(组件的渲染函数或 watch)。
  3. 调度更新 :将 effect 推入微任务队列(避免同步重复执行)。
  4. 执行渲染 :下一个事件循环执行 effect,生成新的虚拟 DOM。
  5. Diff 对比:新旧 VNode 对比,计算最小变更。
  6. DOM 更新:将变更应用到真实 DOM。

六、 高级原理

12. 如何实现自定义渲染器 (Custom Renderer)?

Vue 3 将平台相关的 DOM 操作抽象为渲染器接口 。通过 createRenderer 传入自定义的节点操作函数,可实现渲染到 Canvas、小程序等非 DOM 环境。

13. Teleport 和 Suspense 的实现原理?
  • Teleport :编译时将 <Teleport> 的内容单独提取,在目标容器中渲染,通过 Portal 技术实现挂载到任意 DOM 节点。
  • Suspense :内部包装异步组件,通过 Promise 链跟踪加载状态,协调多个异步依赖的加载状态。

七、 实战原理题

14. 为什么 Vue 3 的 Proxy 比 defineProperty 性能更好?
  • 初始化性能:Proxy 是惰性代理,只在访问时递归;defineProperty 需递归遍历所有属性。
  • 内存占用:Proxy 只需一层代理,defineProperty 需为每个属性创建闭包存储依赖。
15. 如何监听数组变化?

Vue 3 的 Proxy 可直接监听数组的索引变更和 length 变化,无需像 Vue 2 那样重写数组方法。


面试技巧

  • 回答原理题时,结合使用场景性能对比阐述。
  • 提到优化时,务必说出具体技术名词(如 Patch Flag、静态提升)。
  • 如果被追问细节,可从源码角度简述核心类(如 ReactiveEffectrefRefImpl)。

Vue 3 的生命周期分为组合式 API选项式 API 两种写法,功能完全一样,只是写法不同。Vue 3 在 Vue 2 的基础上进行了优化和重命名,使逻辑更清晰。

一、 组合式 API 的生命周期(<script setup>setup() 中)

生命周期钩子 执行时机 使用方式 对应选项式 API 钩子
onBeforeMount 组件挂载到 DOM 前 onBeforeMount(callback) beforeMount
onMounted 组件挂载到 DOM 后 onMounted(callback) mounted
onBeforeUpdate 数据变化,DOM 更新前 onBeforeUpdate(callback) beforeUpdate
onUpdated 数据变化,DOM 更新后 onUpdated(callback) updated
onBeforeUnmount 组件卸载前 onBeforeUnmount(callback) beforeUnmount
onUnmounted 组件卸载后 onUnmounted(callback) unmounted
onErrorCaptured 捕获后代组件错误 onErrorCaptured(callback) errorCaptured
onActivated <KeepAlive> 缓存组件激活时 onActivated(callback) activated
onDeactivated <KeepAlive> 缓存组件失活时 onDeactivated(callback) deactivated
onRenderTracked 开发模式,响应式依赖被收集时 onRenderTracked(callback) 无,Vue 3 新增
onRenderTriggered 开发模式,响应式依赖触发重新渲染时 onRenderTriggered(callback) 无,Vue 3 新增
onServerPrefetch 服务端渲染,组件实例在服务器上被渲染前 onServerPrefetch(callback) serverPrefetch

示例用法

vue 复制代码
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';

const count = ref(0);

onMounted(() => {
  console.log('组件已挂载');
  // 初始化操作,如调用接口、监听事件
  window.addEventListener('resize', handleResize);
});

onBeforeUnmount(() => {
  console.log('组件即将卸载');
  // 清理操作,如移除事件监听器、清除定时器
  window.removeEventListener('resize', handleResize);
});
</script>

二、 选项式 API 的生命周期(与 Vue 2 类似但有变化)

生命周期钩子 执行时机 变化说明
beforeCreate 实例初始化后,数据观测前 不推荐,用 setup 替代
created 实例创建完成,数据观测已建立 不推荐,用 setup 替代
beforeMount 挂载开始之前 不变
mounted 实例挂载完成后 不变
beforeUpdate 数据更新,DOM 打补丁前 不变
updated 数据更新,DOM 打补丁后 不变
beforeUnmount 实例销毁前 重命名 (Vue 2 是 beforeDestroy
unmounted 实例销毁后 重命名 (Vue 2 是 destroyed
errorCaptured 捕获后代组件错误 不变
activated <KeepAlive> 缓存组件激活时 不变
deactivated <KeepAlive> 缓存组件失活时 不变

示例用法

vue 复制代码
<script>
export default {
  data() {
    return { count: 0 };
  },
  mounted() {
    console.log('组件已挂载');
  },
  beforeUnmount() {
    console.log('组件即将卸载');
  }
};
</script>

三、 生命周期执行顺序

组件创建阶段

  1. setup() 执行(组合式 API 的开始)
  2. beforeCreate(选项式 API,不推荐)
  3. created(选项式 API,不推荐)
  4. beforeMount
  5. onBeforeMount
  6. mounted / onMounted

更新阶段(数据变化时):

  1. beforeUpdate
  2. onBeforeUpdate
  3. updated
  4. onUpdated

卸载阶段

  1. beforeUnmount
  2. onBeforeUnmount
  3. unmounted
  4. onUnmounted

四、 重要变化说明

  1. beforeCreatecreated 被替代 :在组合式 API 中,setup() 函数在这两个钩子之前执行,所以应直接在 setup() 中执行初始化逻辑,无需使用这两个钩子。
  2. 钩子重命名beforeDestroybeforeUnmountdestroyedunmounted,命名更准确(销毁的是组件实例,卸载的是 DOM)。
  3. 新增调试钩子onRenderTrackedonRenderTriggered 仅用于开发模式,帮助调试响应式依赖。

五、 最佳实践

  • 组合式 API 优先 :新项目推荐使用组合式 API 的生命周期函数,与 <script setup> 搭配更简洁。
  • 避免混用:不要在同一个组件中混用两种 API 写法。
  • 异步操作位置
    • 数据请求放在 onMountedonCreated(选项式)中。
    • DOM 操作必须放在 onMounted 之后。
    • 清理操作(如事件监听、定时器)放在 onBeforeUnmount 中。
相关推荐
xkxnq1 小时前
第六阶段:Vue生态高级整合与优化(第81天)(Pinia核心进阶)状态模块化设计+跨模块通信(storeToRefs使用避坑)
前端·javascript·vue.js
患得患失9491 小时前
【前端动画】FLIP 动画原则
前端
赵_叶紫2 小时前
Kubernetes 从入门到实践
前端
阿珊和她的猫2 小时前
深入解析浏览器的渲染过程
前端·javascript·vue.js
匠心网络科技2 小时前
JavaScript进阶-ES6 带来的高效编程新体验
开发语言·前端·javascript·学习·面试
Never_Satisfied2 小时前
在HTML & CSS中,nth-child、nth-of-type详解
前端·css·html
睡不着的可乐3 小时前
createElement → VNode 是怎么创建的
前端·javascript·vue.js
光影少年3 小时前
前端css如何实现水平垂直居中?
前端·javascript·css
C澒3 小时前
SLDS 自营物流系统:Pickup 揽收全流程
前端·架构·系统架构·教育电商·交通物流